Skip to content

导引:点亮什么、为什么需要它

002/003 一路到现在,我们的内核一直有个尴尬的限制:它全程 cli,关着中断。这意味着任何 CPU 异常——哪怕是调试时最常用的 int $3 断点——一来就没人接,CPU 直接三重故障重启。这一章,我们给 mini kernel 装上它自己的 GDT 和第一张 IDT,写出异常处理 stub,让它第一次能"被打断"、看一眼出了什么事、然后活着继续。

这一章我们要点亮什么

目标具体而实在:让两条异常被正确接住。第一条是 #BP(向量 3)断点异常,执行 int $3 触发——我们要让它的处理程序把当时的寄存器全部 dump 到串口,然后返回继续执行,因为 int $3 是陷阱,RIP 指向下一条,返回后内核能接着跑。第二条是 #PF(向量 14)页错误——处理程序从 CR2 读出导致缺页的地址、解析错误码的各个位(读还是写、不存在还是权限冲突……),打印出来,然后也继续。

为此要搭三件东西:一张内核自己的 GDT(3 项:空、64 位代码、64 位数据)、一张 IDT(256 项里只填 #BP 和 #PF 两个向量)、以及把异常导流到 C 函数的 ISR stub(汇编)。

做完之后,make run 的串口上会看到 [INIT] GDT loaded[INIT] IDT loaded,然后内核主动 int $3,屏幕打出一段 ==== EXCEPTION: #BP (vector 3) ==== 带着一堆寄存器,最后 [TEST] Breakpoint test passed! Execution continued after #BP.——最后这句"继续执行了"就是整个系统的通过信号:异常被接住、内核没死。

为什么现在需要它

cli 这道闸,在 boot 阶段是保护伞(没有 IDT 时开中断必崩),但到了内核阶段就成了枷锁。一个永远不被打断、一遇异常就重启的内核,没法调试,也没法往上堆任何需要异步的东西(时钟、键盘、系统调用)。

而要"能被打断",内核得先有自己的 IDT——告诉 CPU"向量 3 的异常去这个地址处理、向量 14 去那个地址"。但 IDT 里每个门都要填一个代码段选择子,指向 GDT 里的代码段;bootloader 在 003 建过一张临时 GDT 把我们送进长模式,那张表是"用完即弃"的,内核现在要换上自己的 GDT,IDT 才有可靠的选择子可指。所以顺序是死的:GDT 先,IDT 后

这一章只配两个异常向量,不碰 PIC、不碰硬件中断。注意 int $3 是一条同步指令——它立刻触发 #BP,不需要"开中断"(sti)。事实上这一章全程不开 sti:我们还没有可编程中断控制器(PIC)的驱动,贸然 sti 让定时器中断进来却没人接,当场三重故障。所以这里能演示的是"同步触发的异常被接住",硬件中断要等后面接上 PIC 之后。

外部依据:OSDev 的 Interrupt Descriptor Table / Exceptions 页描述了 64 位 IDT 门描述符格式、#BP/#PF 的语义与错误码各位含义;Intel SDM 规定了中断门(进入时清 IF)与陷阱门(不清 IF)的区别。

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