Skip to content

陷阱和系统调用

有三种事件会导致 CPU 搁置正常的指令执行,并强制将控制权转移到处理该事件的特殊代码。一种情况是系统调用,当用户程序执行 ecall 指令请求内核为其执行某项操作时。另一种情况是异常:一条指令(用户或内核)执行了非法操作,例如除以零或使用无效的虚拟地址。第三种情况是设备中断,当设备发出需要注意的信号时,例如当磁盘硬件完成读或写请求时。

本书使用陷阱作为这些情况的通用术语。通常,在陷阱发生时正在执行的任何代码稍后都需要恢复,并且不应该意识到发生了任何特殊情况。也就是说,我们通常希望陷阱是透明的;这对于设备中断尤其重要,因为被中断的代码通常不期望发生中断。通常的顺序是,陷阱强制将控制权转移到内核;内核保存寄存器和其他状态,以便可以恢复执行;内核执行适当的处理程序代码(例如,系统调用实现或设备驱动程序);内核恢复保存的状态并从陷阱中返回;原始代码从中断处继续执行。

Xv6 在内核中处理所有陷阱;陷阱不会传递给用户代码。在内核中处理陷阱对于系统调用来说是很自然的。对于中断来说,这是有道理的,因为隔离要求只允许内核使用设备,并且因为内核是多个进程之间共享设备的便捷机制。对于异常来说,这也是有道理的,因为 xv6 对所有来自用户空间的异常的响应都是终止有问题的程序。

Xv6 的陷阱处理分为四个阶段:RISC-V CPU 采取的硬件操作、为内核 C 代码准备的一些汇编指令、一个决定如何处理陷阱的 C 函数,以及系统调用或设备驱动程序服务例程。虽然三种陷阱类型之间的共性表明内核可以用单个代码路径处理所有陷阱,但事实证明,为两种不同情况分别设置代码会更方便:来自用户空间的陷阱和来自内核空间的陷阱。处理陷阱的内核代码(汇编或 C)通常被称为处理程序;第一个处理程序的指令通常用汇编(而不是 C)编写,有时被称为向量。