如何來(lái)實(shí)現(xiàn)一個(gè)Linux內(nèi)核的系統(tǒng)調(diào)用(基于tiny4412開(kāi)發(fā)板)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
關(guān)于系統(tǒng)調(diào)用,相信學(xué)習(xí)過(guò)操作系統(tǒng)的同學(xué)應(yīng)該都不陌生。
那么,什么是系統(tǒng)調(diào)用?
百度的權(quán)威解釋如下:
點(diǎn)擊打開(kāi)鏈接
由操作系統(tǒng)實(shí)現(xiàn)提供的所有系統(tǒng)調(diào)用所構(gòu)成的集合即程序接口或應(yīng)用編程接口(Application Programming Interface,API)。是應(yīng)用程序同系統(tǒng)之間的接口。
那么我們編程實(shí)驗(yàn)過(guò)程中使用過(guò)哪些系統(tǒng)調(diào)用呢?
當(dāng)我們要打開(kāi)一個(gè)文件,對(duì)這個(gè)文件進(jìn)行讀寫(xiě)等操作,我們就需要使用open , read , write , lseek等基本的操作函數(shù)API,操作系統(tǒng)中就會(huì)根據(jù)我們的fd(文件句柄)找到對(duì)應(yīng)的open , read , write , lseek函數(shù),在底層進(jìn)行調(diào)用。
舉一個(gè)簡(jiǎn)單的例子:
基于tiny4412實(shí)現(xiàn)的LED驅(qū)動(dòng)和應(yīng)用控制
http://blog.csdn.NET/morixinguan/article/details/50619675
我們?cè)谶@個(gè)例子中就實(shí)現(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 ;
//寫(xiě)write方法就會(huì)調(diào)用到驅(qū)動(dòng)程序的led_write
//最后我們能看到的結(jié)果是led燈做流水燈的實(shí)現(xiàn),然后全滅,再周而復(fù)始
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);
}
在這里,我們通過(guò)open函數(shù),打開(kāi)相應(yīng)的設(shè)備,這里的設(shè)備就是/dev/test-dev,然后對(duì)設(shè)備進(jìn)行寫(xiě)操作,操作系統(tǒng)就會(huì)通過(guò)設(shè)備節(jié)點(diǎn)識(shí)別我們到底調(diào)用了哪個(gè)驅(qū)動(dòng)函數(shù),進(jìn)而實(shí)現(xiàn)一些簡(jiǎn)單的操作。
通過(guò)上層的open函數(shù),內(nèi)核的初始化函數(shù)已經(jīng)對(duì)這個(gè)設(shè)備進(jìn)行了注冊(cè)操作,于是通過(guò)主設(shè)備號(hào)和次設(shè)備號(hào)進(jìn)而調(diào)用了相應(yīng)的驅(qū)動(dòng)函數(shù)led_open,接著write函數(shù)調(diào)用到底層的led_write函數(shù),具體API如下:
//啟動(dòng)函數(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方法,對(duì)LED燈進(jìn)行初始化
int led_open(struct inode *inode, struct file *filp)
{
printk("led_open\n");//上層程序?qū)ED進(jìn)行Open操作的時(shí)候會(huì)執(zhí)行這個(gè)函數(shù)
//先對(duì)LED的端口進(jìn)行清0操作
*led_config &= ~(0xffff);
//將4個(gè)IO口16位都設(shè)置為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)核中進(jìn)行操作,我們需要使用copy_from_user這個(gè)函數(shù)將用戶(hù)態(tài)的內(nèi)容拷貝到內(nèi)核態(tài)
copy_from_user(&val , buf , count);
//以下就是當(dāng)val是哪個(gè)值的時(shí)候,led就執(zhí)行相應(yīng)的操作,這里不多說(shuō)
switch(val)
{
case 0 :
//對(duì)狀態(tài)寄存器進(jìn)行賦值,以下雷同
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)用過(guò)程在前面的字符設(shè)備驅(qū)動(dòng)其實(shí)已經(jīng)說(shuō)得很詳細(xì)就不再闡述。那么,如果我們現(xiàn)在不調(diào)用open,write,read等系統(tǒng)本身有的函數(shù),我們自己來(lái)實(shí)現(xiàn)一個(gè),如何實(shí)現(xiàn)?
以下我們以實(shí)現(xiàn)sys_add()系統(tǒng)調(diào)用來(lái)進(jìn)行過(guò)程描述,這個(gè)API很簡(jiǎn)單,就是通過(guò)上層調(diào)用syscall()函數(shù),傳入兩個(gè)參數(shù),使兩數(shù)相加,具體實(shí)現(xiàn)如下:
1、在內(nèi)核源代碼根目錄找到這個(gè)文件 arch/arm/kernel/calls.S ,打開(kāi)看看:
/* 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)
....
在這個(gè)文件里,聲明我們系統(tǒng)需要調(diào)用的API,我們把相應(yīng)的添加到最后面:
我們把我們需要的添加到最后:
/*376*/ CALL(sys_add) 這里376表示系統(tǒng)調(diào)用號(hào),第376號(hào)
2、在內(nèi)核源代碼根目錄找到這個(gè)文件 arch/arm/include/asm/unistd.h,打開(kāi)看看:
/*
* 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這個(gè)標(biāo)號(hào)375號(hào)后面添加:
#define __NR_add (__NR_SYSCALL_BASE+376)
3、在內(nèi)核源代碼根目錄找到這個(gè)文件 arch/arm/kernel/sys_arm.c , 打開(kāi)看看
在文件的最后添加:
asmlinkage long sys_add(int a, int b)
{
return a+b;
}
這樣,我們就完成了對(duì)底層系統(tǒng)調(diào)用的實(shí)現(xiàn),接下來(lái),我們來(lái)驗(yàn)證我們寫(xiě)的這個(gè)程序的結(jié)果,看看對(duì)不對(duì)。
具體如下:
為了方便驗(yàn)證,這里就不再寫(xiě)應(yīng)用程序,有興趣可以自己去驗(yàn)證,也很簡(jiǎn)單。我們這里采用的還是以驅(qū)動(dòng)的形式進(jìn)行加載。
步驟如下:
1、先在driver目錄下創(chuàng)建一個(gè)目錄: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
//這里通過(guò)一個(gè)指針去獲取系統(tǒng)函數(shù)的入口地址
unsigned long *sys_call_table_add = (unsigned long*)SYS_CALL_ADD_TB;
asmlinkage long sys_add(int a , int b) ; //在這里定義一個(gè)函數(shù)
int __init init_addsyscall(void)
{
int ret ;
sys_call_table_add[376] = sys_add(1,2); //上面定義的這個(gè)函數(shù)作為參數(shù)傳遞給這個(gè)指針
ret = sys_call_table_add[376] ;//獲取到了參數(shù)
printk("System call add loaded ret:%d\n",ret); //執(zhí)行結(jié)果
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下載到板子上,運(yùn)行,我們可以看到串口中打印了相應(yīng)的數(shù)據(jù),是數(shù)字3,也就是1+2的結(jié)果,驗(yàn)證成功。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!