系统调用过程

系统调用

访问内核的方法:

  • 异常
  • 陷入
  • 系统调用

中断

操作系统一般通过中断,从用户态切换到内核态。中断是一个硬件或者软件请求,要求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