Linux添加系统调用
-
前提知识(系统调用如何执行) 先看文章 裁剪Linux内核,用qemu进行调试
- 我们通常写的
c
,有相对应的开源的标准库glibc
,(2.23的glibc中还是有很多缺陷的
)其中的头文件unistd.h
就包含了许多的系统调用,如read/write/open
等等 - 系统调用是通过的修改寄存器
eax
的值,然后通过触发 软中断使系统进入内核空间,比如32位下经典的int 80
以及64位的syscall
。如果你想更清楚的了解系统调用可以查看我之前写的 ret2syscall 。 - 所以内核的中断处理函数是根据系统调用号来调用相应的内核函数,
c
中的read/write
,其实调用了内核函数sys_read/sys_write
- 因此,添加一个系统调用需要注册 系统调用号 以及相应的 中断处理函数,如此,内核才能找到这个系统调用和执行对应的内核函数。内核的汇编代码最终会在
include/generated/asm/syscalls_64.h
中查找调用号,不过我们并不修改这里,x86
平台提供了一个专门用来注册系统调用的文件/arch/x86/entry/syscalls/syscall_64.tbl
,在编译时会运行同目录下的syscalltbl.sh
脚本,将这个文件中登记过的系统调用都生成到前面的syscalls_64.h
文件中。因此我们后面要添加系统调用就是修改这个tbl
文件。 - 系统执行相应功能,调用的是C代码编写的函数,而不是汇编代码,减轻了编写实现系统调用的负担。系统调用的函数定义在
include/linux/syscalls.h
中,因为汇编代码到C代码的参数传递是通过栈实现的,所以可以看到所有系统调用的函数前面都使用了asmlinkage
宏,它意味着编译时限制只使用栈来传递参数。 - 系统调用函数的实现 在
kernel/sys.c
中,当我们注册了相应的系统调用号以及定义了 系统调用的函数 之后,我们就可以在此文件的代码的最后添加自己的函数,这个文件中有很多已经实现的系统调用的函数作为参考。
- 我们通常写的
-
添加系统调用号
- 编辑调用号注册表
cd arch/x86/entry/syscalls/ vi syscall_64.tbl
-
添加系统调用号
#上面省略 328 64 pwritev2 sys_pwritev2 329 common pkey_mprotect sys_pkey_mprotect 330 common pkey_alloc sys_pkey_alloc 331 common pkey_free sys_pkey_free 332 common statx sys_statx 333 common ps_counter sys_ps_counter # 下面的我们x32先不管,系统调用号,就在330左右,在此行上面添加 # x32-specific system call numbers start at 512 to avoid cache impact # for native 64-bit operation. #
-
定义系统调用函数
-
编辑系统调用函数原型定义
vi include/linux/syscalls.h
-
添加函数原型(__user 是为了向用户空间传值)
#以上省略 asmlinkage long sys_pkey_mprotect(unsigned long start, size_t len, unsigned long prot, int pkey); asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val); asmlinkage long sys_pkey_free(int pkey); asmlinkage long sys_ps_counter(int __user *num); asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags, unsigned mask, struct statx __user *buffer); #endif
-
-
编写实现函数
-
实现函数文件
vi kernel/sys.c
-
实现函数(这里就只要统计task数量到用户空间,我顺便输出了内核相关的task的信息,可以做调试用)
SYSCALL_DEFINE1(ps_counter, int __user*, num) { struct task_struct *task; int counter=0; printk("[Syscall] ps_counter\n"); printk("Now pid = %ld",current->pid);#current是宏,指向当前task printk("Pid Parent->pid"); for_each_process(task){ counter++; printk(" %ld %ld\n",task->pid,task->parent->pid); } copy_to_user(num, &counter, sizeof(int)); #结果显示到用户空间 return 0; }
-
-
编写测试代码
- 源文件
vi test_syscall.c
#include <unistd.h> #include <syscall.h> #include <stdio.h> int main(void){ int result; syscall(333,&result); //系统调用号,是你自己在`tbl`中注册的 printf("process number is %d\n",result); return 0; }
- 编译(一定静态编译,因为你的qemu下的linux可能没有这些库)
gcc -static -o get_ps_num get_ps_num.c
- 将编译好的二进制,放到
busybox
下的_install
下的tmp
文件夹中,重新压缩gpio
文件,这一步看之前的文章 裁剪Linux内核,用qemu进行调试
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
-
运行测试程序
-
编译添加完系统调用号,定义了系统调用函数,完成了函数实现之后,编译
linux
内核sudo make -j8
-
使用新编译的内核以及刚打包的根文件系统,启动虚拟机
qemu-system-x86_64 -kernel ~/Desktop/linux-4.14/arch/x86/boot/bzImage -initrd ~/Desktop/busybox-1.32.1/initramfs.cpio
-