给linux添加系统调用——从源头说起

来源:互联网 发布:软件需求说明书案例 编辑:程序博客网 时间:2024/05/17 15:05

给linux添加系统调用,网上一搜一大堆,我很久之前也试图添加过并且按照网上的教程一步一步做,但是都没有成功过,因为网上的教程大体都是一个样子,一份创造多分副本,照着做也不明白为什么,而且linux内核版本从2.6跨越到3.x之后的目录结构的差异也比较大,同一种方法在另一个内核中不见得就能够行得通。本文讲解的内核采用的linux2.6.32.63版本。

本文试图从系统调用的概念入手,然后再来添加系统调用。理解了这个原理远比照着教程做出效果来却不知其所以然更重要。

linux通过int 0x80指令产生中断,内核会查找中断向量表来执行相应的中断函数。在中断函数中内核通过查找函数指针数组,取出下标为%eax的指针元素并执行,这就是系统调用的主要步骤。

int 0x80是一条汇编指令,通常用在内联汇编中,如(参见《程序员的自我修养》第四章:最小的程序):

char *str = "Hello world!\n";asm( "movl $13, %%edx \n\t"        "movl %0, %%ecx \n\t"        "movl $1, %%ebx \n\t"        "movl $4, %%eax \n\t"        "int $0x80             \n\t"         ::"r"(str): "edx", "ecx", "ebx");
内联汇编语法可以参考《linuxC编程一站式学习》第19章:C内联汇编。

上面的代码实现了write的系统调用,当执行到int 0x80的时候产生中断,中断函数执行4号系统调用,在32位系统上该函数为sys_write(),它有三个参数,分别是%ebx、%ecx、%edx,它们的值是1、str、13,这样内核执行的函数就是sys_write(1, str, 13)。即向标准输出文件写str,执行后我们会在屏幕上面看到"Hello world"的字样。

这样看来是不是很简单?

x86的中断向量表在linux源码的arch/x86/kernel/traps.c里可见一部分,函数trap_init()用于初始化中断向量表,在它的末尾可以看到:

函数set_system_trap_gate用于设置某个中断号上的处理程序,在该函数中还有其他几种设置方式,这些方式适用范围也不一样,这里我们关心的是SYSCALL_VECTOR和system_call这两个符号。在arch/x86/include/asm/irq_vectors.h中可以找到:


可以知道0x80号中断执行的函数就是system_call,在arch/x86/kernel/entry_64.S我找到了该函数的入口(32位路径为arch/x86kernel/entry_32.S):

可以看到截图倒数第二行:call *sys_call_table(,%rax,8) 这条指令的意思就是call *(sys_call_table+8*%rax)。sys_call_table是一个函数指针数组,每个指针占8位,取其中第%rax个指针并执行。这就是我们用户态所谓的中断调用号,上例中我们的中断调用号是4,在32位系统上是sys_write的系统调用。

sys_call_table数组定义在文件arch/x86/kernel/syscall_64.c中,其定义如下:

很明显这是个指针数组,它的赋值方式很特别,举个例子:

type_t array[ARRAYSIZE + 1] = {    [0 ... ARRAYSIZE] = element,    [0] = element1,    [1] = element2,    ...};
结果中的0和1元素会被覆盖,其他的元素都是element。这样的语法在c++是不支持的。我们看看#include <asm/unistd_64.h>里面有什么:

将上图中的宏展开后得到和上例形式相同的数组,这两张图里面用到了很多的宏定义,读者可以留心观察一番。最终sys_call_table数组就包含了该文件所列举的所有系统调用函数。细心的读者肯定已经发现了这个数组的大小__NR_systcall_max+1了,我们添加系统调用并不需要改动这个值,看看它的定义arch/x86/kernel/asm-offsets_64.c:

所以sys_call_table数组的大小就是#include <asm/unistd.h>里面的函数的个数,如果需要添加自己的函数在这个头文件添加即可。细心的读者已经发现我上图在末尾添加的自己的函数。到这里基本上就大功告成了,读者们可以理一理思路。

剩下的工作就是声明并定义我们添加的函数。内核提供了一个宏定义来定义系统调用SYSCALL_DEFINEx(),我们只需要是用这个宏来定义自己的系统调用。定义的位置最好跟你的函数实现的功能关联,比如你要实现文件读写相关的功能那么你可以定义在fs/open.c等文件中,下面是我的定义fs/open.c:

系统调用的声明在include/linux/syscalls.h:

编译安装内核(网上的方法很多可以参考网上的最后可以执行make modules_install install来完成自动安装)。

安装完毕!

测试用户态程序:

成功添加,读者可以在此基础上添加更多的功能。


0 0
原创粉丝点击