1. 与内核通信
系统调用是用户空间进程和硬件设备的中间层,作用:
- 提供抽象接口
- 保证系统稳定安全
- 作为用户空间访问内核的唯一入口
2. 系统调用
如何定义系统调用:
- 添加限定词
asmlinkage
(编译指令),通知编译器仅从栈中提取参数 - 函数返回
long
,兼容32位和64位系统 - 命名规则:以
sys_
开头
2.1. 系统调用号
每个系统调用的调用号唯一,一旦分配就不准变更,且删除系统调用的调用号不能回收利用。
针对无效系统调用(填补空缺),有
sys_ni_syscall()
,仅返回-ENOSYS
注册的系统调用列表存在sys_call_table
,x86-64定义于arch/i386/kernal/syscall_64.c
中。
2.2. 系统调用性能
要比其它OS快且简洁,因此Linux上下文切换时间短。
3. 系统调用处理程序
用户空间不能直接访问和执行内核代码(内核空间受保护),因此使用软中断通知内核。
x86中,使用指令int $0x80
(第128号中断程序),将系统切换到内核态,而该中断程序是系统调用处理程序。(程序在entry_64.S
文件中编写)
3.1. 指定系统调用
x86上,系统调用号通过eax
传递,检查通过后,调用:call *sys_call_table(, %rax, 8)
3.2. 参数传递
前5个参数用ebx
,ecx
,edx
,esi
,edi
顺序存储,若参数过多,应指定一个寄存器作为指针,它指向存放参数的用户空间地址。
返回值也通过寄存器传递,x86是eax
。
4. 系统调用实现
可参考OS实验课
5. 系统调用上下文
内核执行系统调用式处于进程上下文,current
指向引发系统调用的进程。
由于Linux内核可抢占,因此执行系统调用时内核可以休眠且被抢占。因此编写系统调用一定要注意可重入。
系统调用返回时,控制权仍在system_call()
(系统调用入口函数),它会负责切换到用户空间并继续执行用户进程。