Skip to content

导引:点亮什么、为什么、设计图

这一章我们要点亮什么

我们要让 big kernel 第一次拥有"扛事"的能力。落到一个看得见的效果上:在 main 里执行一句 int $3 触发断点异常,串口打印出完整的寄存器 dump,然后——这是重点——内核没死,它从异常里返回,接着往下跑,吐出一句 Breakpoint returned, continuing

听起来不起眼,但对一个裸奔的内核来说,这是从"一碰就碎"到"能自我诊断"的分水岭。没有这层兜底,后面随便一个 #PF、#GP 都会换来一次莫名其妙的重启,连死因都看不着。

为什么现在需要它

009 时候的 big kernel,本质上是个只会 kprintf 的裸奔程序。它脚下的临时 GDT 够它蹭两步,可 CPU 一旦抛出任何异常——除零、缺页、非法指令——没有 IDT 就意味着没有 handler,CPU 找不到落点,连续触发下去就是三重故障,QEMU 直接重启。调试这种状态是噩梦,因为你根本不知道自己错在哪。

我们要做的,是在内核和"彻底崩溃"之间塞进一张安全网。这张网就是 IDT,中断描述符表:它告诉 CPU,"当异常 X 发生时,去调用这个函数"。而 IDT 里每个 gate 最终都要跳进一段代码段去执行——这就用上了上一篇铺好的 GDT 选择子。所以两篇的顺序是锁死的,GDT 先就绪,IDT 才接得上。

设计图

先看 IDT gate 长什么样。它和 GDT 的段描述符不一样,是 16 字节一个,干的事就一件:把"异常发生时该跳去哪"编码进去。

text
字节:    0        2        4     5          6        8         12        16
      ┌─────────┬────────┬─────┬──────────┬────────┬─────────┬─────────┐
      │ offset  │selector│ IST │ type_attr│ offset │ offset  │ reserved│
      │  low    │  (CS)  │     │          │  mid   │  high   │         │
      │ (16b)   │ (16b)  │(8b) │   (8b)   │ (16b)  │  (32b)  │  (32b)  │
      └─────────┴────────┴─────┴──────────┴────────┴─────────┴─────────┘

handler 的地址被拆成三段(低 16、中 16、高 32)塞进 gate,因为一个 64 位地址没法直接放进连续字段;selector 是这个 handler 要跑在哪个代码段,就是我们上一篇的 GDT_KERNEL_CODE;IST 决定它用哪个中断栈;type_attr 编码"这是个什么 gate"。

异常一来,CPU 干的事大致是这样一条链:先把 SS/RSP/RFLAGS/CS/RIP(再外加可能的 error code)压进栈,然后按 IDT[向量号] 找到 gate,把 gate.selector 装进 CS,跳去 handler。handler 那头是我们写的汇编 stub,它保存通用寄存器,把栈指针当参数传给 C 函数,C 函数读完现场再返回,stub 恢复寄存器、跳过 error code、iretq,CPU 就把刚才压的那些东西弹回去,回到被打断的地方接着跑。整条链我们下面逐段拆。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04