之前一直不太懂系统调用,总结一下方便自己学习。
系统调用是用户空间进程和内核之间的中间层,它的作用有以下两点:
- 为用户空间提供硬件的抽象接口。当应用程序需要读写文件的时候,可以不用去管文件所在的文件系统的类型;
- 保证系统的稳定和安全。内核基于权限、用户类型和其他一些规则对需要进行的访问进行裁决,避免应用程序错误的使用硬件设备或者危及系统安全。
在Linux系统中,系统调用是用户空间访问内核的唯一手段。
系统调用号
在Linux中,每个系统调用都有一个系统调用号,通过系统调用号来关联系统调用。当用户空间的进程执行一个系统调用时,用系统调用号来指明所使用的系统调用。
内核记录了系统调用表中所有已注册过的系统调用的列表,存储在sys_call_table
中。每种体系结构都明确定义了该表,在x86-64
中,它定义于arch/i386/kernel/syscall_64.c
文件中,该表为每一个有效的系统调指定了唯一的系统调用号。
系统调用的性能
Linux很短的上下文切换时间使得其系统调用比多数操作系统执行的快,进出内核都被优化的简洁高效。
系统调用处理程序
用户空间的程序无法直接执行内核代码,不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。因此,应用程序以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。
通知内核的机制是靠软中断实现的。通过引发一个异常来促使系统切换到内核态去执行异常处理程序,此时的异常处理程序就是系统调用处理程序。在内核中有一个中断向量表数组,该数组中是中断处理程序的指针。中断到来时,CPU暂停执行中的代码,根据中断号在中断向量表中找到对应的中断处理程序并调用它。在x86系统上预定义的软中断是中断号128,通过int $0x80
指令触发该中断,该指令会触发一个异常导致系统切换到内核态并执行128号异常处理程序,叫system_call()
,它与硬件体系结构紧密相关。
在陷入内核之前,用户空间把相应系统调用所对应的系统调用号放入eax
寄存器中,通过这种方式将系统调用号传递给内核,系统调用程序运行时就可以从eax
中得到数据。system_call()
函数通过比较NR_syscalls
(系统调用的总数)和给定的系统调用号来检查其有效性,如果该系统调用号大于等于NR_syscalls
,则返回ENOSYS
,否则执行系统调用call *sys_call_table(,%rax,8)
。
除系统调用号以外,大部分系统调用还需要一些外部参数输入。所以在发生陷入的时候,需要把这些参数从用户空间传递给内核。其与传递系统调用号的方式相同,将参数存放在寄存器中。
总结
当操作系统收到系统调用请求后:
- 根据中断号在中断向量表中找到对应的中断处理程序并调用;
- 根据
eax
寄存器中的系统调用号以及输入的外部参数来执行对应的系统调用函数; - 将结果返回给用户代码。