理解中断

硬件上的中断:一根线

  • $\overline{\textrm{IRQ}}$, $\overline{\textrm{NMI}}$ (边沿触发,低电平有效)
  • “告诉处理器:停停,有事来了”
    • 剩下的行为交给处理器处理

实际的处理器并不是 “无情地执行指令”

  • 无情的执行指令
  • 同时有情地响应外部的打断
    • 你在图书馆陷入了自习的死循环……
    • 清场的保安打断了你

处理器的中断行为

“响应”

  • 首先检查处理器配置是否允许中断
    • 如果处理器关闭中断,则忽略
  • x86 Family
    • 询问中断控制器获得中断号 n
    • 保存 CS, RIP, RFLAGS, SS, RSP 到堆栈
    • 跳转到 IDT[n] 指定的地址,并设置处理器状态 (例如关闭中断)
  • RISC-V (M-Mode)
    • 检查 mie 是否屏蔽此次中断
    • 跳转 PC = (mtvec & ~0xf)
    • 更新 mcause.Interrupt = 1

RISC-V M-Mode 中断行为 RTFSC

“更多的处理器内部状态”

struct MiniRV32IMAState {
  uint32_t regs[32], pc;
  uint32_t mstatus;
  uint32_t cyclel, cycleh;
  uint32_t timerl, timerh, timermatchl, timermatchh;
  uint32_t mscratch, mtvec, mie, mip;
  uint32_t mepc, mtval, mcause;
  // Note: only a few bits are used.  (Machine = 3, User = 0)
  // Bits 0..1 = privilege.
  // Bit 2 = WFI (Wait for interrupt)
  // Bit 3+ = Load/Store reservation LSBs.
  uint32_t extraflags;
};

中断:奠定操作系统 “霸主地位” 的机制

操作系统内核 (代码)

  • 想开就开,想关就关

应用程序

  • 对不起,没有中断
    • 在 gdb 里可以看到 flags 寄存器 (FL_IF)
    • CLI — Clear Interrupt Flag
      • #GP(0) If CPL is greater than IOPL and less than 3
      • 试一试 asm volatile ("cli");
  • 死循环也可以被打断

AbstractMachine 中断 API

隔离出一块 “处理事件” 的代码执行时空

  • 执行完后,可以返回到被中断的代码继续执行
bool cte_init(Context *(*handler)(Event ev, Context *ctx));
bool ienabled(void);
void iset(bool enable);

中断引入了并发

  • 最早操作系统的并发性就是来自于中断 (而不是多处理器)
  • 关闭中断就能实现互斥
    • 系统中只有一个处理器,永远不会被打断
    • 关中断实现了 “stop the world”

真正的计算机系统模型

状态

  • 共享内存和每个处理器的内部状态 $(M, R_1, R_2, \ldots, R_n)$

状态迁移

  1. 处理器 $t$ 执行一步 $(M, R_t) \to (M', R_t')$
    • 新状态为 $(M', R_1, R_2, \ldots, R_t', \ldots, R_n) $
  2. 处理器 $t$ 响应中断

假设 race-freedom

  • 不会发生 relaxed memory behavior
  • 模型能触发的行为就是真实系统能触发的所有行为

例子:实现中断安全的自旋锁

实现原子性:

printf("Hello\n");
  • printf 时可能发生中断
  • 多个处理器可能同时执行 printf

这件事没有大家想的简单

  • 多处理器和中断是两个并发来源