每个系统调用都会有一个编号与之相对应。用户态调用一个系统调用,实际上调用运行时库(glibc)中的函数,runtime library会通过80号中断,陷入内核态,通过eax寄存器传递要调用系统调用编号,参数的传递通过寄存器或者copy_to_user等函数,实际通过页面缓存进行。然后根据调用号找到系统调用对应的内核函数的名字。
所有系统以及对应的调用号定义在:arch/x86/include/asm/unistd_32.h。在这里,我们要将系统调用的名字与编号相对应。
每个系统调用号与对应的内核函数的关系在: arch/x86/kernel/syscall_table_32.S中,这种对应关系是通过偏移来实现的,内核函数在这个表中的位置即是调用号。也就说通过调用号我们可以找到对应的内核函数。
内核函数的声明在文件:include/linux/syscalls.h
查找的过程是,首先在arch/x86/include/asm/unistd_32.h中寻找对应的系统调用的名字,得到对应的系统调用号,然后到了内核态,系统调用号就是在arch/x86/kernel/syscall_table_32.S表中的偏移,我们会得到实际的系统调用对应的内核函数。
那么给系统添加一个系统调用的过程如下:
1 在文件arch/x86/include/asm/unistd_32.h中添加
#define _NR_mysyscall 326 //不能与之前的已经重复,这个编号就是系统调用号,内核函数在 arch/x86/kernel/syscall_table_32.S表中的偏移。
#define _NR_syscalls 327 //修改总共的系统调用数目
2在arch/x86/kernel/syscall_table_32.S中末尾添加系统调用对应的内核函数.类似于:
.long sys_mycall
其偏移应当就是第一步定义的系统调用号
3 在include/linux/syscalls.h添加内核函数的声明:
类似于: asmlinkage long sys_mycall(int);
其实现需要在usr/src/linux/下创建一个文件夹mysyscall,在该文件夹中定义文件mysyscall.c,内容大概如下:
#include<linux/linkage.h>
asmlinkage long sys_mycall(int i)
{
return i+10;
}
asmlinkage是告诉编译器在内核栈上寻找参数。
4 调用该内核函数:
#include<linux/unistd.h>
#define __NR_mysyscall 317
_syscall1(long, mysyscall, int, i)
上面的代码使得生成了如下的系统调用:
long mysyscall(int i)
{
return syscall(__NR_mysyscall, i);
}
内核中提供了不同的syscall函数.
syscall需要传递系统调用号和一个参数,就会调用该系统调用号对应的内核函数。
而宏_syscall1需要的四个参数分别是返回值的类型,系统调用的名称,参数的类型,参数的名称。 系统调用号则是传递进来的名称前+__NR对应的宏的值。
也就说提供给用户态的系统调用名称最终是生成的,glibc中提供的很多系统调用就是直接通过脚本生成的。
syscall的含义,用法在不同的内核版本之间可能已经不同,但提供的功能大致相当。
5 动态修改系统调用的方法,大概过程是,设法获取到内存中运行的内核模块的syscall_table.s指定偏移的地址。修改或者添加一个新的子项,就是一个系统调用号对应的新的内核函数了