注意:这篇文章上次更新于605天前,文章内容可能已经过时。
This article was last updated605 days ago, the content may be outdated.
系统调用是用户空间和内核空间之间的接口,是用户程序与操作系统之间的桥梁。
Linux 内核提供了一系列的系统调用,用户程序可以通过这些系统调用来请求内核执行某些操作。
比如,open 系统调用用于打开一个文件,read 系统调用用于读取文件内容,write 系统调用用于写入文件内容等。
本文将介绍如何为 Linux Kernel 添加一个新的系统调用。
添加系统调用
为 Linux Kernel 添加系统调用的步骤如下:
- 修改
arch/x86/entry/syscalls/syscall_64.tbl文件,添加新的系统调用号。
系统调用号是一个整数,用于标识系统调用。
具体来说,我们需要在 syscall_64.tbl 文件中添加一行,格式如下:
1 | <number> <abi> <name> <entry point> [<compat entry point> [noreturn]] |
number 是系统调用号, 它是一个整数, read 的系统调用号是 0, write 的系统调用号是 1, 以此类推
abi 的全称是 Application Binary Interface,它通常可以指定为 common,64 或 x32
name 是系统调用的名称
entry point 是系统调用的入口地址
我们在这个文件中添加一个新的系统调用,用来获取当前机器的 CPU 数量。
1 | 463 common get_cpu_num sys_get_cpu_num |

- 在
include/linux/syscalls.h文件中声明新的系统调用。
在 syscalls.h 文件中添加一个新的系统调用声明,格式如下:
1 | asmlinkage long sys_get_cpu_num(void); |

为什么要使用 asmlinkage 呢?asmlinkage 确保系统调用函数的参数通过堆栈传递,而不是通过寄存器。这在某些架构或特定调用约定下是必要的。尽管在 x86_64 架构上,系统调用的参数通常通过寄存器传递,但使用 asmlinkage 可以确保在不同架构上参数传递的一致性。
示例
假设我们有一个系统调用 sys_my_syscall,它接受两个参数 int arg1 和 int arg2。在 x86_64 架构上
在 x86_64 架构上,系统调用的参数通常通过寄存器传递:
1
2
3
4 asmlinkage long sys_my_syscall(int arg1, int arg2) {
// 参数 arg1 和 arg2 通过寄存器传递
return arg1 + arg2;
}在这个例子中,arg1 和 arg2 会通过 rdi 和 rsi 寄存器传递。
在 x86 (32-bit) 架构上
在 x86 (32-bit) 架构上,系统调用的参数通过堆栈传递:
1
2
3
4 asmlinkage long sys_my_syscall(int arg1, int arg2) {
// 参数 arg1 和 arg2 通过堆栈传递
return arg1 + arg2;
}在这个例子中,arg1 和 arg2 会通过堆栈传递。
- 在
kernel/sys.c文件中实现新的系统调用。
在 sys.c 文件中实现新的系统调用,格式如下:
1 | SYSCALL_DEFINE0(get_cpu_num) |
SYSCALL_DEFINE0 是一个宏,用于定义一个参数个数为 0 的系统调用。
num_present_cpus 是一个内核函数,用于获取当前机器的 CPU 数量。

相应地,也存在 SYSCALL_DEFINE1、SYSCALL_DEFINE2、SYSCALL_DEFINE3 等宏,用于定义参数个数为 1、2、3 的系统调用。
例如:
1 | SYSCALL_DEFINE1(umask, int, mask) |
SYSCALL_DEFINE1 宏用于定义一个参数个数为 1 的系统调用,umask 是系统调用的名称,int 是参数的类型,mask 是参数的名称。
测试系统调用
添加系统调用后需要重新编译内核,如果你不知道怎么编译内核,请先阅读 Linux Kernel 从编译到运行
make mrproper清理内核源码目录make menuconfig配置内核

make -j8 bzImage编译内核
将编译好的内核镜像文件拷贝到前一篇文章中workspace 目录下。
- 编写测试程序
在 workspace/initramfs 目录下创建一个测试程序 test_sys_call.c,内容如下:
1 |
|
- 编译测试程序
因为我们在 QEMU 中运行测试程序,所以在编译时需要采用静态链接的方式。
静态链接就是将程序所需要的库文件全部打包到可执行文件中,这样在运行时就不需要再去加载库文件。
1 | gcc -static test_sys_call.c -o test_sys_call |
此时我的workspace目录结构如下:
1 | $ tree -L 2 |

- 运行测试程序
我们要在 QEMU 中运行测试程序,首先修改一下 Makefile 文件,为 QEMU 启动时设置 CPU 数量。
-smp n 参数用于设置 CPU 数量。
1 |
|
然后我们依次执行以下命令:
1 | make clean |
启动 QEMU 后,我们可以在 QEMU 中运行测试程序:

修改 Makefile 文件中的 -smp 1 为 -smp 4,然后重新执行 make run 命令,再次执行测试程序:

可以看到此时CPU数量变成了 4, 同时出现了 Kernel Panic,暂时先不管它。
修改 -smp 15,然后重新执行 make run 命令,再次执行测试程序:

可以看到此时CPU数量变成了 15。
总结
本文介绍了如何为 Linux Kernel 添加一个新的系统调用,以及如何测试系统调用。
- 系统调用是一个和CPU架构相关的概念,不同的CPU架构有不同的系统调用号。
- x86 架构下添加一个新的系统调用的步骤是:修改
arch/x86/entry/syscalls/syscall_64.tbl文件,添加新的系统调用号;在include/linux/syscalls.h文件中声明新的系统调用;在kernel/sys.c文件中实现新的系统调用。


