系统调用
访问内核的方法:
- 异常
- 陷入
- 系统调用
中断
操作系统一般通过中断,从用户态切换到内核态。中断是一个硬件或者软件请求,要求CPU暂停当前的工作。举一些例子:
- 时间片中断
- 在x86机器上,通过int指令进行软件中断
- 磁盘完成读写后,向CPU发起硬件中断
中断属性 | 作用 |
---|---|
中断号 | 识别 |
中断处理程序 | 处理 |
在操作系统内核中维护着一个中断向量表(Interrupt Vector Table),这个数组中存储了所有中断处理程序的地址,而中断号就是相应中断在中断向量表中的偏移量
处理中断的原则:中断不可嵌套,越快处理越好,中断嵌套可能会导致栈溢出,处理中断并不是在用户栈中执行,而是在内核栈中执行,空间并不算充裕
流程
步骤 | 描述 |
---|---|
用户态 | |
getpid()/fork()... |
调用用户空间下暴露的函数接口,一个函数内部可能包含一个或多个系统调用 |
int syscall_env_destroy(u_int envid) { return msyscall(SYS_env_destroy, envid); } |
把系统调用号,调用参数,转发给msyscall |
syscall j ra |
汇编调用syscall,系统调用处理结束之后跳回msyscall |
CPU | |
CPU 自动将指令指针RIP保存到寄存器中RCX。RIP指向应用程序中应在系统调用完成后执行的指令 | 最小的上下文保存 |
将包含CPU当前状态的RFLAGS寄存器保存到寄存器中R11。这包括中断启用/禁用标志等标志 | |
CPU载入模型特定寄存器(MSR)IA32_LSTAR,其中包含内核中系统调用入口点的地址,直接跳转到系统调用处理程序 | 至此,完成了内核态的切换,下面开始在内核模式下执行系统调用的程序 |
内核态 | |
SAVE_ALL |
把寄存器的状态保存在一个Trapframe结构体中,位于KSTACKTOP |
void (*exception_handlers[32])(void) = { [0 ... 31] = handle_reserved, [0] = handle_int, [2 ... 3] = handle_tlb, [1] = handle_mod, [8] = handle_sys, }; |
在exception_handler中查到handle_sys的地址 |
jal do_syscall |
跳转到do_syscall(struct Trapframe* tf) |
tf->regs[2] = func(arg1, arg2, arg3, arg4, arg5); |
从Trapframe中提取信息,并转到对应的系统调用处 |
一次系统调用执行完毕之后,程序流不一定会返回,可能进程直接就挂起了,等待下一次被schedule