本文实际上是浙江大学操作系统课程实验二的一个记录,所以会有一些很奇怪的操作。
实验目的
添加一个系统调用 mysyscall,获取系统中 Page Fault 的次数。基于 Linux Kernel 4.6 的代码完成。
实验方法
这里需要说明一下的是 include/uapi/asm-generic/unistd.h
、include/linux/syscalls.h
、 arch/x86/entry/syscalls/syscall_64.tbl
、arch/x86/entry/syscalls/syscall_32.tbl
的区别。unistd.h
是一个 POSIX 标准的头文件,其中包含了 POSIX 标准的系统调用的函数封装;syscalls.h
是 Linux 系统调用真实的声明文件;syscall_<32/64>.tbl
是对应架构的系统调用列表,这里存在 32 和 64 两个版本是因为 x86 架构有 x86_64 这样的扩展。
因此,如果我们需要在一台 x86_64 架构的 Linux 系统中添加一个系统调用,我们对于以上的文件都需要做出修改。但是需要注意的一点是,他们中的系统调用号不必相同,但是最终在 user mode 中使用该系统调用的时候,使用的应该是对应版本 tbl 中的编号。比如一个 x86_64 架构的 PC,最终调用的编号应该和 syscall_64.tbl
中的编号相同。
接下来我们可以开始添加系统调用了,假设我们系统调用的名字为 mysyscall
,实现函数体的原型为 asmlinkage long sys_mysyscall(void);
- 在
include/linux/syscalls.h
中写入系统调用函数的声明,注意需要写在最后一行 #endif 之前。比如我们这里就需要加入asmlinkage long sys_mysyscall(void);
- 在
include/uapi/asm-generic/unistd.h
中写入 POSIX 标准通用的系统调用声明,虽然类似于 x86 这样的架构有自己的系统调用表,但是我们应该保持兼容。这里我们需要加入这样的两行:#define __NR_mysyscall 292 __SYSCALL(__NR_mysyscall, sys_mysyscall)
- 在
kernel/sys_ni.c
中写入刚才加入的系统调用,这个提供了一个 fallback stub implementation,返回值是-ENOSYS
(Function not implemented.)cond_syscall(sys_mysyscall);
- 在
arch/x86/entry/syscalls/syscall_64.tbl
中写入刚才添加的系统调用,如果不需要在 x86 和 x86_64 上有不同的表现,则应该将属性定为 common
340 common mysyscall sys_mysyscall
- 在
arch/x86/entry/syscalls/syscall_32.tbl
中也加入类似的东西,这是为 i386 架构服务的
380 i386 mysyscall sys_mysyscall
- 实现 Page Fault 统计
在include/linux/mm.h
文件中声明变量pfcount
:extern unsigned long pfcount;
在进程task_struct
中增加成员pf
,在include/linux/sched.h
文件中的task_struct
结构中添加pf
字段unsigned long pf;
在进程创建过程中将pf
字段置 0,修改kernel/fork.c
文件中的dup_task_struct()
函数:static struct task_struct *dup_task_struct(struct task_struct *orig) { …… tsk = alloc_task_struct_node(node); if (!tsk) return NULL; …… ++ tsk->pf=0; …… }
在
arch/x86/mm/fault.c
文件中定义变量 pfcount;并修改arch/x86/mm/fault.c
中do_page_fault()
函数。每次产生缺页中断,do_page_fault()
函数会被调用,pfcount
变量值递增1,记录系统产生缺页次数,current->pf
值递增 1,记录当前进程产生缺页次数:… ++ unsigned long pfcount; __do_page_fault(struct pt_regs *regs, unsigned long error_code) { … ++ pfcount++; ++ current->pf++; … }
sys_mysyscall
的实现
在usr/src/linux-4.6
下建立mysyscall
文件夹,其中建立三个文件Makefile
、mysyscall.c
和mysyscall.h
,分别如下:
mysyscall.hasmlinkage long sys_mysyscall(void);
mysyscall.c
#include<linux/kernel.h> #include<linux/init.h> #include<linux/sched.h> #include<linux/syscalls.h> #include "mysyscall.h" asmlinkage long sys_mysyscall(void) { printk("Current Page Fault Count: %lu\n", current->pf); return 0; }
Makefile
obj-y:=mysyscall.o
- 修改主 Makefile 以编译 mysyscall
找到 usr/src/linux-4.6/Makefile 中这样一行 core -y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/,改为 core -y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ mysyscall/ - 编译安装内核
sudo make bzImage -j $(nproc) && sudo make modules -j $(nproc) && sudo make modules_install && sudo make install && sudo update-grub
- 重启
- 编写用户态程序,由于我本地是 x86_64 架构,因此使用刚才设定的 340 号系统调用
#include <stdio.h> #include <linux/kernel.h> #include <sys/syscall.h> #include <unistd.h> #define __NR_mysyscall 340 int main() { long ret = syscall(__NR_mysyscall); printf("Return Value: %ld\n", ret); return 0; }
- 在 /var/log/kern.log 中查看输出,或者直接使用 dmesg 查看输出
一些额外的收获
/usr/src/linux-4.6/scripts/checksyscall.sh 是一个很神奇的脚本,它对于所有的代码进行 gcc 预处理之后,判断系统调用是否被正确写入和注册,如果这个测试不能通过,编译成功后也不一定能正常运行该系统调用。
查看 #include 命令调用的真实文件:echo '#include <unistd.h>' | gcc -E -x c -
,会得到类似于 # 1 "/usr/include/unistd.h" <some numbers>
这样的输出,意思是后面这些 number 标记的行的代码来自于 /usr/include/unistd.h
如果要看真实的引用文件,可以这么用:echo '#include <unistd.h>' | gcc -E -x c - | egrep '# [0-9]+ ' | awk '{print $3;}' | sort -u
kern.log 是 Line-Buffered,因此每个 printk 之后需要换行才能在 dmesg 中看到输出
编译内核模块的时候如果发现 arch/x86/Makefile:148: CONFIG_X86_X32 enabled but no binutils support 这样的错误,首先检查一下路径中是否有空格,如果有空格会引发这样的错误
参考资料
Linux Kernel 4.6 代码
https://unix.stackexchange.com/questions/1697/how-can-i-know-which-unistd-h-file-is-loaded
https://www.kernel.org/doc/html/v4.12/process/adding-syscalls.html
http://www.cs.fsu.edu/~cop4610t/lectures/project2/system_calls/system_calls.pdf
https://medium.com/@ssreehari/implementing-a-system-call-in-linux-kernel-4-7-1-6f98250a8c38
https://stackoverflow.com/questions/11237420/error-syscall-function-not-implemented