6.6: Locks and interrupt handlers

注释:该内容基于原版课程的textbook和源代码,在大模型辅助翻译的基础上进行的整理。

一些 xv6 自旋锁用于保护线程和中断处理程序都需要访问的数据。例如,clockintr 定时器中断处理程序可能会在内核线程在 sys_sleep (kernel/sysproc.c:61) 中读取 ticks 的同时递增 ticks (kernel/trap.c:164)。

锁 tickslock 将这两次访问序列化。自旋锁和中断的交互带来潜在的危险。假设 sys_sleep 持有 tickslock,并且其 CPU 被一个定时器中断打断。clockintr 会尝试获取 tickslock,发现其已被持有,并等待其释放。

在这种情况下,tickslock 将永远不会被释放:只有 sys_sleep 才能释放它,但 sys_sleep 在 clockintr 返回之前不会继续运行。因此,CPU 会死锁,任何需要该锁的代码也会冻结。

为了避免这种情况,如果自旋锁被中断处理程序使用,CPU 必须在持有该锁时禁用中断。xv6 更为保守:当 CPU 获取任何锁时,xv6 会在该 CPU 上禁用中断。中断仍可能在其他 CPU 上发生,因此中断的 acquire 可以等待线程释放自旋锁;只是在同一 CPU 上不会发生。

当 CPU 不再持有自旋锁时,xv6 会重新启用中断;它必须进行一些记录来应对嵌套的关键区域。acquire 调用 push_off (kernel/spinlock.c:89),release 调用 pop_off (kernel/spinlock.c:100) 来跟踪当前 CPU 上锁的嵌套层级。当计数降为零时,pop_off 恢复最外层关键区域开始时的中断启用状态。intr_off 和 intr_on 函数分别执行 RISC-V 指令来禁用和启用中断。

重要的是,acquire 必须在设置 lk->locked (kernel/spinlock.c:28) 之前严格调用 push_off。如果顺序颠倒,会有一个短暂的窗口期,此时锁被持有且中断启用,一个不合时宜的中断将导致系统死锁。同样重要的是,release 必须在释放锁之后才调用 pop_off (kernel/spinlock.c:66)。

push_off(); // disable interrupts to avoid deadlock.

Appendx:中断产生的机制及相关中断设置

系统中断的机制是指当处理器正在执行程序时,硬件或软件可以发送信号来中断当前的执行流程,以处理紧急任务或进行上下文切换。中断机制是现代计算机系统实现多任务处理和快速响应外部事件的重要手段。

  1. 系统中断的产生机制:
    硬件中断:由外部设备(如键盘、网络接口、定时器等)发送到处理器的信号引发。例如,当用户按下键盘时,会触发一个硬件中断,处理器中断当前正在执行的程序,转而执行对应的中断服务程序(Interrupt Service Routine, ISR)。
    软件中断:由程序触发的中断,通常用于系统调用或其他需要切换到操作系统内核的操作。软件中断通过特殊的指令(如 int 指令)发起,常用于请求操作系统服务。
    异常(Exception):处理器在执行程序时遇到错误(如除零、非法指令)时会产生异常,属于一种特殊的中断。

  2. 关闭和打开中断:
    处理器通常有专门的控制寄存器或标志位来启用和禁用中断。例如:

关闭中断:处理器中有指令来禁止中断。通常是通过设置特定的状态寄存器来实现。以 x86 架构为例,可以使用 CLI(Clear Interrupt Flag)指令来禁用中断,在此指令之后处理器不会响应任何中断请求,直到中断被重新启用。

打开中断:对应地,使用 STI(Set Interrupt Flag)指令来打开中断,使处理器可以响应中断请求。

在 RISC-V 和其他架构中,有类似的控制中断的指令或机制。例如:

RISC-V:intr_off() 和 intr_on() 等函数通过修改特定寄存器的中断使能位来禁用或启用中断。它们通常会操作 mstatus 寄存器中的 MIE 位(Machine Interrupt Enable)来控制中断。

中断关闭和打开的用途: 关闭中断:用于保护关键代码段,避免在执行关键任务时被中断打断,防止数据竞争和不一致。
打开中断:在完成关键任务或临界区后,恢复中断,以便处理器能够正常响应其他外部或内部事件。
通过在合适的时机启用或禁用中断,系统能更好地处理并发任务和保证数据一致性,同时避免死锁和数据损坏。