如何來實現(xiàn)一個Linux內(nèi)核的系統(tǒng)調(diào)用(基于tiny4412開發(fā)板)
關于系統(tǒng)調(diào)用,相信學習過操作系統(tǒng)的同學應該都不陌生。
那么,什么是系統(tǒng)調(diào)用?
百度的權威解釋如下:
點擊打開鏈接
由操作系統(tǒng)實現(xiàn)提供的所有系統(tǒng)調(diào)用所構成的集合即程序接口或應用編程接口(Application Programming Interface,API)。是應用程序同系統(tǒng)之間的接口。
那么我們編程實驗過程中使用過哪些系統(tǒng)調(diào)用呢?
當我們要打開一個文件,對這個文件進行讀寫等操作,我們就需要使用open , read , write , lseek等基本的操作函數(shù)API,操作系統(tǒng)中就會根據(jù)我們的fd(文件句柄)找到對應的open , read , write , lseek函數(shù),在底層進行調(diào)用。
舉一個簡單的例子:
基于tiny4412實現(xiàn)的LED驅(qū)動和應用控制
http://blog.csdn.NET/morixinguan/article/details/50619675
我們在這個例子中就實現(xiàn)了系統(tǒng)調(diào)用:
fd = open("/dev/test-dev",O_RDWR) ;
if(-1 == fd)
{
printf("open fair!\n");
return -1 ;
}
while(1){
val = 0 ;
//寫write方法就會調(diào)用到驅(qū)動程序的led_write
//最后我們能看到的結果是led燈做流水燈的實現(xiàn),然后全滅,再周而復始
write(fd , &val , 4);
sleep(1);
val = 1 ;
write(fd , &val , 4);
sleep(1);
val = 2 ;
write(fd , &val , 4);
sleep(1);
val = 3 ;
write(fd , &val , 4);
sleep(1);
val = 5 ;
write(fd , &val , 4);
sleep(1);
}
在這里,我們通過open函數(shù),打開相應的設備,這里的設備就是/dev/test-dev,然后對設備進行寫操作,操作系統(tǒng)就會通過設備節(jié)點識別我們到底調(diào)用了哪個驅(qū)動函數(shù),進而實現(xiàn)一些簡單的操作。
通過上層的open函數(shù),內(nèi)核的初始化函數(shù)已經(jīng)對這個設備進行了注冊操作,于是通過主設備號和次設備號進而調(diào)用了相應的驅(qū)動函數(shù)led_open,接著write函數(shù)調(diào)用到底層的led_write函數(shù),具體API如下:
//啟動函數(shù)
static __init int test_init(void)
{
printk("led_init\n");
major = register_chrdev(major, DEV_NAME, &fops);
led_config = (volatile unsigned long *)ioremap(GPM4COM , 16);
led_dat = led_config + 1 ;
return 0;
}
//open方法,對LED燈進行初始化
int led_open(struct inode *inode, struct file *filp)
{
printk("led_open\n");//上層程序?qū)ED進行Open操作的時候會執(zhí)行這個函數(shù)
//先對LED的端口進行清0操作
*led_config &= ~(0xffff);
//將4個IO口16位都設置為Output輸出狀態(tài)
*led_config |= (0x1111);
return 0;
}
//write方法
int led_write(struct file *filp , const char __user *buf , size_t count , loff_t *f_pos)
{
int val ;
//注意,這里是在內(nèi)核中進行操作,我們需要使用copy_from_user這個函數(shù)將用戶態(tài)的內(nèi)容拷貝到內(nèi)核態(tài)
copy_from_user(&val , buf , count);
//以下就是當val是哪個值的時候,led就執(zhí)行相應的操作,這里不多說
switch(val)
{
case 0 :
//對狀態(tài)寄存器進行賦值,以下雷同
printk(KERN_EMERG"led1_on\n");
*led_dat &= ~0x1 ;
break ;
case 1 :
printk(KERN_EMERG"led2_on\n");
*led_dat &= ~0x2 ;
break ;
case 2 :
printk(KERN_EMERG"led3_on\n");
*led_dat &= ~0x4 ;
break ;
case 3 :
printk(KERN_EMERG"led4_on\n");
*led_dat &= ~0x8 ;
break ;
case 4 :
printk(KERN_EMERG"ledall_on\n");
*led_dat &= ~0xf ;
break ;
case 5 :
printk(KERN_EMERG"ledall_off\n");
*led_dat |= 0xf ;
break ;
}
}
上述調(diào)用過程在前面的字符設備驅(qū)動其實已經(jīng)說得很詳細就不再闡述。那么,如果我們現(xiàn)在不調(diào)用open,write,read等系統(tǒng)本身有的函數(shù),我們自己來實現(xiàn)一個,如何實現(xiàn)?
以下我們以實現(xiàn)sys_add()系統(tǒng)調(diào)用來進行過程描述,這個API很簡單,就是通過上層調(diào)用syscall()函數(shù),傳入兩個參數(shù),使兩數(shù)相加,具體實現(xiàn)如下:
1、在內(nèi)核源代碼根目錄找到這個文件 arch/arm/kernel/calls.S ,打開看看:
/* 0 */ CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
CALL(sys_write)
/* 5 */ CALL(sys_open)
CALL(sys_close)
....
在這個文件里,聲明我們系統(tǒng)需要調(diào)用的API,我們把相應的添加到最后面:
我們把我們需要的添加到最后:
/*376*/ CALL(sys_add) 這里376表示系統(tǒng)調(diào)用號,第376號
2、在內(nèi)核源代碼根目錄找到這個文件 arch/arm/include/asm/unistd.h,打開看看:
/*
* This file contains the system call numbers.
*/
#define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)
#define __NR_exit (__NR_SYSCALL_BASE+ 1)
#define __NR_fork (__NR_SYSCALL_BASE+ 2)
#define __NR_read (__NR_SYSCALL_BASE+ 3)
#define __NR_write (__NR_SYSCALL_BASE+ 4)
#define __NR_open (__NR_SYSCALL_BASE+ 5)
#define __NR_close (__NR_SYSCALL_BASE+ 6)
....
在__NR這個標號375號后面添加:
#define __NR_add (__NR_SYSCALL_BASE+376)
3、在內(nèi)核源代碼根目錄找到這個文件 arch/arm/kernel/sys_arm.c , 打開看看
在文件的最后添加:
asmlinkage long sys_add(int a, int b)
{
return a+b;
}
這樣,我們就完成了對底層系統(tǒng)調(diào)用的實現(xiàn),接下來,我們來驗證我們寫的這個程序的結果,看看對不對。
具體如下:
為了方便驗證,這里就不再寫應用程序,有興趣可以自己去驗證,也很簡單。我們這里采用的還是以驅(qū)動的形式進行加載。
步驟如下:
1、先在driver目錄下創(chuàng)建一個目錄:yyx_syscall
依次創(chuàng)建syscall_add.c Makefile
往syscall_add.c添加代碼:
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/sched.h>
#include<asm/uaccess.h>
#include<linux/compiler.h>
#include<linux/linkage.h>
#include<linux/types.h>
#include<linux/unistd.h>
//在linux內(nèi)核根目錄下找到System.map中sys_add的地址
#define SYS_CALL_ADD_TB 0xc004e30c
//這里通過一個指針去獲取系統(tǒng)函數(shù)的入口地址
unsigned long *sys_call_table_add = (unsigned long*)SYS_CALL_ADD_TB;
asmlinkage long sys_add(int a , int b) ; //在這里定義一個函數(shù)
int __init init_addsyscall(void)
{
int ret ;
sys_call_table_add[376] = sys_add(1,2); //上面定義的這個函數(shù)作為參數(shù)傳遞給這個指針
ret = sys_call_table_add[376] ;//獲取到了參數(shù)
printk("System call add loaded ret:%d\n",ret); //執(zhí)行結果
return 0;
}
void __exit exit_addsyscall(void)
{
printk("System call unlodaded\n");
}
module_init(init_addsyscall);
module_exit(exit_addsyscall);
MODULE_LICENSE("GPL");
Makefile內(nèi)容如下:
obj-y += syscall_add.o
然后回到內(nèi)核的根目錄下:
make -j4
將編譯生成的zImage下載到板子上,運行,我們可以看到串口中打印了相應的數(shù)據(jù),是數(shù)字3,也就是1+2的結果,驗證成功。
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!