用户程序请求内核程序为其服务主要通过以下几种方式:
中断
系统调用
信号
其中,系统调用是一种常见方式,它在用户进程与硬件之间提供了一个层,该层主要提供以下三个目的:
它为用户空间提供了一个抽象的硬件接口
它确保了系统的安全与稳定性。
为虚拟化系统的实现提供支持。
操作系统内核提供了许多系统调用接口,一个典型的系统调用过程如下:
在x86平台上,系统调用是通过软件中断来实现的,中断号为128(或0x80)。系统调用需要提供系统调用号(传递给eax)以及一些参数(依次传递给ebx,ecx, edx, esi, edi),系统调用处理函数通常名为system_call(),定义在entry.S或entry_64.S中。它会检查系统调用号的合法性,即是否大于NR_syscalls,如果是的话,返回-ENOSYS,否则调用对应的函数:call*sys_call_table(,%rax,8)
自定义一个系统调用
在Linux中实现一个系统调用不用户关心系统调用处理函数的行为,因此增加一个系统调用非常容易。
SYSCALL_DEFINE0~6分别声明一个参数为0~6个的系统调用。
定义完系统调用函数后,剩下的工作就是将其注册为一个内核系统调用函数:
在系统调用表中末尾添加一项,通常赋给该系统调用一个调用号(即在entry.S中的ENTRY(sys_call_table))。
对每个支持的平台,在<asm/unistd.h>中定义系统调用号。
将系统调用编译到内核镜像中(而不是编译成一个模块),可以将系统调用函数放在kernel/sys.c文件中。
例子如下,我们要定义一个foo系统调用函数:
/* * sys_foo – everyone’s favorite system call. * * Returns the size of the per-process kernel stack. */ asmlinkage long sys_foo(void)// SYSCALL_DEFINE0(sys_foo) { return THREAD_SIZE; }
添加foo到entry.S文件中:
ENTRY(sys_call_table) .long sys_restart_syscall /* 0 */ .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open /* 5 */ … .long sys_rt_tgsigqueueinfo /* 335 */ .long sys_perf_event_open .long sys_recvmmsg .long sys_foo
我们的系统调用号为:338
在<asm/unistd.h>
增加宏定义:
#define__NR_foo 338
在用户空间中调用,_syscall0~6对应不同参数个数的系统调用
#define __NR_foo 283 __syscall0(long, foo) int main () { long stack_size; stack_size = foo (); printf (“The kernel stack size is %ld\n”, stack_size); return 0; }