本文章以x86-32为例:
1
2
3 1$ uname -a
2Linux localhost 2.6.32 #1 SMP Sat Jun 13 23:55:06 CST 2015 i686 i686 i386 GNU/Linux
3
第一步,在系统调用表中加入一个表项,
表中为每一个有效的系统调用指定了惟一的系统调用号。系统调用表位于
arch/x86/kernel/syscall_table_32.S文件中,在该文件中最后一行添加自己的系统调用表项,如下:
1
2
3
4
5
6
7 1......
2 .long sys_preadv
3 .long sys_pwritev
4 .long sys_rt_tgsigqueueinfo /* 335 */
5 .long sys_perf_event_open
6 .long sys_shallnet
7
虽然没有明确指定编号,但该系统调用已经按次序分配了337这个系统调用号,接下来就应该添加系统调用号。
第二步,添加系统调用号。
在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。在文件
arch/sh/include/asm/unistd_32.h该列表中加入一行**#define __NR_shallnet 337 **
:
1
2
3
4
5
6
7
8
9
10
11
12
13 1......
2#define __NR_inotify_init1 332
3#define __NR_preadv 333
4#define __NR_pwritev 334
5#define __NR_rt_tgsigqueueinfo 335
6#define __NR_perf_event_open 336
7
8#define __NR_shallnet 337
9
10//#define NR_syscalls 337
11#define NR_syscalls 338
12......
13
在内核源文件中该行为#define NR_syscalls 337,在系统调用执行的过程中,system_call()函数会根据该值来对用户态进程的有效性进行检查。如果这个号大于或等于NR_syscalls,系统调用处理程序终止。所以应该将原来的#define NR_syscalls 337修改为#define NR_syscalls 338。
第三步,实现shallnet系统调用。在文件kernel/sys.c最后添加如下函数:
1
2
3
4
5
6 1SYSCALL_DEFINE0(shallnet)SYSCALL_DEFINE1(shallnet, int, arg)
2{
3 printk(KERN_ALERT"My blog address: \"http://blog.csdn.net/shallnet\"");
4 return arg + arg;
5}
6
第四步,重新编译内核。依次执行:
1
2
3
4
5
6 1make oldconfig
2make bzImage
3make modules
4make modules_install
5make install
6
第五步,重新启动系统然后进入刚新编译的系统,编写测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1#include <stdio.h>
2#include <unistd.h>
3#include <sys/syscall.h>
4
5#define __NR_shallnet 337
6
7int main(int argc, const char *argv[])
8{
9 int ret;
10
11 ret = syscall(337, 99);
12
13 printf("shallnet() return: %d\n", ret);
14 return 0;
15}
16
编译执行该程序如下:
1
2
3
4
5
6
7 1$ ./target_bin
2shallnet() return: 198
3
4$ demsg
5......
6
7
可以看到我们新加的系统调用执行成功了。
可见建立一个新的系统调用还是很容易的,但是不提倡这么做,系统调用需要一个系统调用号,需要修改内核代码,修改之后需要重新编译内核。linux系统应当尽量避免每出现一个新的抽象就加入一个新的系统调用,通常有其他的方法可以代替系统调用,比如说实现一个设备节点等。