现在的位置: 首页 > 综合 > 正文

编写一个系统调用

2014年10月21日 ⁄ 综合 ⁄ 共 2597字 ⁄ 字号 评论关闭

如何往内核中添加自己写的系统调用?其实步骤非常简单:

原文:http://edsionte.com/techblog/archives/2086

1.编写一个系统调用;
2.在系统调用表末尾加入一个新表项;
3.在< asm/unistd.h >中添加一个新的系统调用号;
4.重新编译内核;

上述工作完成后,就可以在用户程序中使用自己所编写的系统调用了。接下来,我们将逐步分析如何上述步骤。

1.编写系统调用

我们将要实现一个获得当前进程pid的的系统调用。对于一个进程,我们可以直接通过current->pid来获得。为了使得这个系统调用同样适用于一个线程,我们使用current->tgid。这么做的原因是同一个线程组内的所有线程TGID均相同;而一个进程的PID和TGID是相同的。

1 asmlinkage long
sys_mygetpid(void)
2 {
3     return
current->tgid;
4 }

与普通函数不同的是,这个系统调用的函数名前有asmlinkage修饰符。这个修饰符使得GCC编译器从堆栈中取该函数的参数而不是寄存器中。另外,系统调用函数的命名规则都是sys_XXX的形式。

接下来,我们的要做的是将这个函数放于何处。一种方法是,将上述函数放于/kernel/下的某个文件中;另一种方式是,将这个函数单独存放在/kernel/下的一个新建的.c文件中。不管何种方法,所做的目的都是为了在重新编译内核时将上述我们所编写的系统调用函数编译进内核。

2.在系统调用表中添加新的表项

linux中为每一个系统调用都分配一个系统调用号。也就是说,每一个系统调用号都唯一的对应着一个系统调用。内核中使用系统调用表来记录所有已经注册过的系统调用,这个系统调用表存储在sys_call_table中。在yoursource/arch/x86/kernel/syscall_table_32.S中可以看到系统系统调用表。

我们所要做的就是在该表的末尾添加我们刚编写的系统调用:.long sys_getpid。我们并不需要显式的指定系统调用号,从0开始算起,我们所编写的函数的系统调用号为341。

01 ENTRY(sys_call_table)
02        .long
sys_restart_syscall       /* 0 - old "setup()" system call, used for restarting */
03        .long
sys_exit
04        .long
ptregs_fork
05        .long
sys_read
06        .long
sys_write
07        .long
sys_open          /* 5 */
08           …… ……
09       .long
sys_perf_event_open
10       .long
sys_recvmmsg
11       .long
sys_fanotify_init
12       .long
sys_fanotify_mark
13       .long
sys_prlimit64             /* 340 */
14       .long
sys_mygetpid

3.在< asm/unistd.h >中添加一个新的系统调用号

上一步,在系统调用表中添加新的表项是为了将系统调用号和系统调用关联起来。现在,我们需要在unistd.h文件中添加具体的系统调用号,这样使得系统可以根据这个系统调用号在系统调用表中查找具体的系统调用。

当用户进程调用一个系统调用时,其实是通过一个中断号为128的软中断来通知内核的。此时,内核会由用户态切换到内核态转而去执行这个软中断对应的中断处理程序。而这个中断处理程序恰好就是系统调用处理程序。也就是说,任何系统调用都会引发CPU去执行这个系统调用处理程序。因此,必须通过系统调用号来识别不同的系统调用。系统调用号通常会存储在eax寄存器中。

现在我们就在yoursource/arch/x86/include/asm/unistd_32.h文件中添加新的系统调用号。在上一步我们所添加的sys_mygetpid系统调用对应的编号为341,因此我们在该文件的末尾添加下面的语句:#define __NR_mygetpid 341。注意这里的宏命名规则,以__NR_开头。

1 #define __NR_restart_syscall      0
2 #define __NR_exit                 1
3 #define __NR_fork                 2
4 #define __NR_read                 3
5 #define __NR_write                4
6        …………
7 #define __NR_fanotify_mark      339
8 #define __NR_prlimit64          340
9 #define __NR_mygetpid         341

4.编译内核

如果上述三个步骤都完成后,那么接下来重新编译内核即可。具体可参见这里

5.编写用户态的程序

1 #include < linux/unistd.h >
2  
3 _syscall0(int,mygetpid)
4  
5 int main()
6 {
7     printf("The current process's pid is %d\n",mygetpid());
8     return
0;
9 }

上述用户程序可能与我们平日里所写的稍有不同。主要区别是增加了_syscall0(int,mygetpid)这个宏。因为我们现在直接在程序中调用系统调用,而我们平时则是通过调用C库中的API来间接调用系统调用。在unistd.h文件中有_syscallN()宏的定义,这里的N可取0~6。N代表的是需要传递给系统调用的参数个数。由于mygetpid系统调用需传递的参数个数为0,因此选取_syscall0。另外,这组宏的内部参数分布有如下特点:第一个参数是系统调用返回值的类型,第二个参数是系统调用函数的名称,接下来的参数按照系统调用参数的次序依次是参数类型和参数名称。对于每个宏来说,都有2+2*N个参数。

OK,上述方法即可以将我们自己编写的系统调用函数加入到内核。try!

抱歉!评论已关闭.