Skip to content

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

这一章我们要点亮什么

落到一个看得见的效果上:big kernel 跑起来后,串口开始每秒稳定吐一行——

text
[TICK] uptime: 1s
[TICK] uptime: 2s
[TICK] uptime: 3s

不是 kprintf 死循环打出来的,而是真正的硬件中断:PIT 每秒触发 100 次 IRQ0,第 100、200、300……次各打一行。内核的执行流被一个外部信号反复「打断」又「接住」,而它全程没崩。这对一个裸奔内核来说,是从「只能被自己主动 int」跨到「能响应外部世界」的质变。

顺带我们还要确认一件事:上一章的异常网没坏。所以在开中断之前,main 会再 int $3 一次,证明 IDT、PIC、IRQ 这套新东西装上去之后,老的异常处理照样能正常返回。

为什么现在需要它

010 的内核是「聋」的。它听得见 CPU 自己抛的异常(#BP、#PF 这类),但听不见任何硬件——没有时钟、没有键盘、没有串口输入。更要命的是,它主动拒绝听见:main 末尾的 idle loop 是 cli; hlt,把中断整个关死,然后停机。这是一种诚实的自保,因为那时候哪怕只开一条 IRQ,都会立刻 Double Fault。

为什么一开就崩?因为 x86 的硬件中断向量默认会撞进 CPU 的异常区。8259 PIC 上电默认把 IRQ0-15 映射到 INT 8-15(0x08-0x0F),而 8-15 这段正好是 #DF#TS#GP#PF 这些异常的家。PIT 一旦开始嘀嗒,IRQ0 就被当成 INT 0x08 推给 CPU,CPU 一看「这是 Double Fault 的向量」,可又没有真正的 #DF 语义去匹配,直接三重故障重启。

所以这章的真正主题不是「加个定时器」,而是「先把 16 条硬件中断从异常区里搬出来」。这个搬迁动作叫 PIC remap,是整章的地基。搬完之后,IRQ0-15 落到 0x20-0x2F 这段没人用的向量上,再去配 PIT、开中断,才安全。

设计图

先看硬件长什么样。PC 上是两片 8259A 级联,主片接 CPU,从片通过主片的 IRQ2 接上来,一共 15 条可用 IRQ 线(IRQ2 被级联占了):

text
                 ┌──────────────────────┐
   CPU INTR ◄────┤  Master PIC          │  IRQ0  PIT 计时器
                 │  cmd  0x20 / data 0x21│  IRQ1  键盘
                 │                       │  IRQ2 ──┐ 级联线
                 │                       │  IRQ3-7 │
                 └───────────────────────┘         │

                                   ┌──────────────────────┐
                                   │  Slave PIC           │  IRQ8   RTC
                                   │  cmd  0xA0/data 0xA1 │  IRQ9-15
                                   └──────────────────────┘

remap 之后,IRQ 号和 CPU 看到的中断向量号不再是同一个东西。我们要让硬件 IRQ 经过 PIC 翻译,落到 0x20 起的这一段:

text
  硬件 IRQ        PIC 翻译后 CPU 看到的 INT 向量
  ─────────       ────────────────────────────────
  IRQ0  (PIT)  →  INT 0x20      ┐
  IRQ1  (键盘) →  INT 0x21      │  Master 片
  IRQ2  (级联) →  INT 0x22      │  IRQ0-7 → 0x20-0x27
  ...                           ┘
  IRQ8  (RTC)  →  INT 0x28      ┐
  ...                           │  Slave 片
  IRQ15        →  INT 0x2F      ┘  IRQ8-15 → 0x28-0x2F

一次 IRQ0 从触发到返回的完整链路,是这章所有代码要串起来的东西:

text
  PIT 计数到 0 ──► 拉 IRQ0

                  Master PIC ──► 向 CPU 发 INT 0x20

                          CPU 压栈(SS/RSP/RFLAGS/CS/RIP),跳 IDT[0x20]

                          irq0_stub(汇编):保存通用寄存器,传 frame,call pit_irq0_handler

                          PIT::irq0_handler:tick++,每秒打一行,PIC::send_eoi(0)

                          stub 恢复寄存器 ──► iretq ──► 回到被打断处

注意链路最后那段 send_eoi(0)——它是这条链能不能持续运转的关键,下面专门讲。

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