操作系统学习14 IDT + PIC + 键盘中断

操作系统学习14 IDT + PIC + 键盘中断

  • 一、本章说明
    • [1. 前置知识](#1. 前置知识)
    • [2. 本章目标](#2. 本章目标)
    • [3. 目录结构](#3. 目录结构)
    • [4. 与 lesson13 的差异](#4. 与 lesson13 的差异)
  • 二、IDT:中断来了跳去哪
    • [1. 为什么需要 IDT](#1. 为什么需要 IDT)
      • [中断门描述符(Interrupt Gate)的硬件细节](#中断门描述符(Interrupt Gate)的硬件细节)
      • 门描述符里保存:
    • [2. 向量号布局](#2. 向量号布局)
    • [3. 代码:注册 IRQ1 门描述符](#3. 代码:注册 IRQ1 门描述符)
  • [三、8259A 可编程中断控制器(PIC)](#三、8259A 可编程中断控制器(PIC))
    • [1. 8259A 是什么](#1. 8259A 是什么)
    • [2. 主从级联:最多 15 路 IRQ](#2. 主从级联:最多 15 路 IRQ)
    • [3. IRQ 线 vs 中断向量号](#3. IRQ 线 vs 中断向量号)
    • [3.1 为什么必须「重映射」(本章最核心)](#3.1 为什么必须「重映射」(本章最核心))
    • [4. 端口与寄存器组](#4. 端口与寄存器组)
      • [4.1 两类控制字](#4.1 两类控制字)
        • [ICW 的核心功能与分类](#ICW 的核心功能与分类)
        • [ICW 与 OCW 的区别](#ICW 与 OCW 的区别)
      • [4.2 内部关键寄存器(不必直接读,理解即可)](#4.2 内部关键寄存器(不必直接读,理解即可))
    • [5. 初始化命令字 ICW1 ~ ICW4](#5. 初始化命令字 ICW1 ~ ICW4)
      • [ICW1 --- 启动初始化(写命令端口)](#ICW1 — 启动初始化(写命令端口))
      • [ICW2 --- 中断向量基址(写数据端口)](#ICW2 — 中断向量基址(写数据端口))
      • [ICW3 --- 级联关系(写数据端口)](#ICW3 — 级联关系(写数据端口))
      • [ICW4 --- 附加模式(写数据端口)](#ICW4 — 附加模式(写数据端口))
      • [初始化后立刻设置 IMR](#初始化后立刻设置 IMR)
    • [6. 完整 init_pic 与 ICW 对照](#6. 完整 init_pic 与 ICW 对照)
    • [7. 一次键盘中断的完整流程](#7. 一次键盘中断的完整流程)
    • [8. EOI(End Of Interrupt)](#8. EOI(End Of Interrupt))
      • [非特定 EOI(本章使用)](#非特定 EOI(本章使用))
      • [特定 EOI(了解)](#特定 EOI(了解))
      • [从片 IRQ 的 EOI](#从片 IRQ 的 EOI)
    • [9. 运行期 OCW 操作(本章未用,供扩展)](#9. 运行期 OCW 操作(本章未用,供扩展))
    • [10. BIOS 默认 vs 本章重映射](#10. BIOS 默认 vs 本章重映射)
  • [四、8253 可编程间隔定时器(PIT)](#四、8253 可编程间隔定时器(PIT))
    • [1. 8253 是什么](#1. 8253 是什么)
    • [2. 输入时钟与 3 个通道](#2. 输入时钟与 3 个通道)
      • [2.1 输入频率](#2.1 输入频率)
      • [2.2 三个通道在 PC 上的用途](#2.2 三个通道在 PC 上的用途)
    • [3. I/O 端口](#3. I/O 端口)
    • [4. 控制字(Control Word)格式](#4. 控制字(Control Word)格式)
    • [5. 六种工作模式(MODE 0~5)](#5. 六种工作模式(MODE 0~5))
    • [6. 初始化 Channel 0 示例(下一章参考)](#6. 初始化 Channel 0 示例(下一章参考))
    • [7. 常用分频对照表](#7. 常用分频对照表)
    • [8. Channel 2 与 PC 扬声器(了解)](#8. Channel 2 与 PC 扬声器(了解))
    • [9. 一次 IRQ0 定时中断的流程](#9. 一次 IRQ0 定时中断的流程)
    • [10. 本章与 8253 的关系](#10. 本章与 8253 的关系)
  • [五、8042 与键盘中断使能](#五、8042 与键盘中断使能)
  • [六、IRQ1 处理程序与环形缓冲](#六、IRQ1 处理程序与环形缓冲)
    • [1. 为何用缓冲](#1. 为何用缓冲)
    • [2. irq1_handler](#2. irq1_handler)
    • [3. read_char:缓冲优先,端口兜底](#3. read_char:缓冲优先,端口兜底)
  • 七、kernel.c
  • 八、编译与运行
    • 按键无反应排查
      • [0. 你的日志说明什么](#0. 你的日志说明什么)
      • [1. 先排除低级问题](#1. 先排除低级问题)
      • [2. 用断点定位「中断有没有触发」](#2. 用断点定位「中断有没有触发」)
      • [3. 对照 lesson13(最快二分)](#3. 对照 lesson13(最快二分))
      • [4. 查 PIC 与向量号(最常见根因)](#4. 查 PIC 与向量号(最常见根因))
      • [5. 查 8042 是否有数据(中断以外的证据)](#5. 查 8042 是否有数据(中断以外的证据))
      • [6. 现象对照](#6. 现象对照)
  • 九、常见问题
  • [十、PIC_REMAP 必须为 1:踩坑与修复](#十、PIC_REMAP 必须为 1:踩坑与修复)
    • [1. 问题现象](#1. 问题现象)
    • [2. 根因(一句话)](#2. 根因(一句话))
    • [3. 修复](#3. 修复)
    • [4. 教训总结](#4. 教训总结)
  • 十一、本章小结

一、本章说明

1. 前置知识

请先阅读:

本章在 lesson13 基础上,把键盘输入从 CPU 空转轮询 升级为 IRQ1 硬件中断驱动

2. 本章目标

完成本章后,我们将能够:

  • 构造 IDT (中断描述符表),用 LIDT 告诉 CPU 中断向量对应的处理程序
  • 初始化 8259A PIC(ICW1~4 重映射、IMR 屏蔽),把 IRQ 映射到向量 32~47
  • 编写 IRQ1 键盘中断处理程序 :读 0x60 扫描码、写入环形缓冲、向 PIC 发 EOI
  • init_keyboard 末尾 STI 开中断read_charHLT 休眠 等待按键,不再占用 CPU
  • 理解 8253 PIT 三通道、端口 0x40~0x43 及与 PIC IRQ0 的关系(本章屏蔽 IRQ0,下一章启用)

3. 目录结构

复制代码
lesson14/
├── Makefile
├── boot/
│   ├── loader.s        # 与 lesson13 相同:读盘 + 进保护模式
│   └── pm.inc          # 段/门描述符宏
├── kernel/
│   ├── kernel.c        # 欢迎语 + 提示符 + 键盘回显(同 lesson13)
│   ├── utilities.s     # IDT + PIC + IRQ1 + 环形缓冲
│   └── utilities.h
└── README.md

4. 与 lesson13 的差异

项目 lesson13 lesson14
读键方式 循环 IN 0x64 轮询 IRQ1 中断 + 环形缓冲
IDT 256 项 IDT,注册向量 33
PIC 8259 重映射 + 仅开放 IRQ1
CPU 等待 空转轮询 .wait 缓冲优先;空则读 8042(Bochs 兜底)
8042 0xAE + 0xF4 额外 置命令字节 bit0 允许键盘中断
kernel.c 相同 相同

#mermaid-svg-qRNQZttBTfYwLBnd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qRNQZttBTfYwLBnd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qRNQZttBTfYwLBnd .error-icon{fill:#552222;}#mermaid-svg-qRNQZttBTfYwLBnd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qRNQZttBTfYwLBnd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qRNQZttBTfYwLBnd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qRNQZttBTfYwLBnd .marker.cross{stroke:#333333;}#mermaid-svg-qRNQZttBTfYwLBnd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qRNQZttBTfYwLBnd p{margin:0;}#mermaid-svg-qRNQZttBTfYwLBnd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qRNQZttBTfYwLBnd .cluster-label text{fill:#333;}#mermaid-svg-qRNQZttBTfYwLBnd .cluster-label span{color:#333;}#mermaid-svg-qRNQZttBTfYwLBnd .cluster-label span p{background-color:transparent;}#mermaid-svg-qRNQZttBTfYwLBnd .label text,#mermaid-svg-qRNQZttBTfYwLBnd span{fill:#333;color:#333;}#mermaid-svg-qRNQZttBTfYwLBnd .node rect,#mermaid-svg-qRNQZttBTfYwLBnd .node circle,#mermaid-svg-qRNQZttBTfYwLBnd .node ellipse,#mermaid-svg-qRNQZttBTfYwLBnd .node polygon,#mermaid-svg-qRNQZttBTfYwLBnd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qRNQZttBTfYwLBnd .rough-node .label text,#mermaid-svg-qRNQZttBTfYwLBnd .node .label text,#mermaid-svg-qRNQZttBTfYwLBnd .image-shape .label,#mermaid-svg-qRNQZttBTfYwLBnd .icon-shape .label{text-anchor:middle;}#mermaid-svg-qRNQZttBTfYwLBnd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qRNQZttBTfYwLBnd .rough-node .label,#mermaid-svg-qRNQZttBTfYwLBnd .node .label,#mermaid-svg-qRNQZttBTfYwLBnd .image-shape .label,#mermaid-svg-qRNQZttBTfYwLBnd .icon-shape .label{text-align:center;}#mermaid-svg-qRNQZttBTfYwLBnd .node.clickable{cursor:pointer;}#mermaid-svg-qRNQZttBTfYwLBnd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qRNQZttBTfYwLBnd .arrowheadPath{fill:#333333;}#mermaid-svg-qRNQZttBTfYwLBnd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qRNQZttBTfYwLBnd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qRNQZttBTfYwLBnd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qRNQZttBTfYwLBnd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qRNQZttBTfYwLBnd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qRNQZttBTfYwLBnd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qRNQZttBTfYwLBnd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qRNQZttBTfYwLBnd .cluster text{fill:#333;}#mermaid-svg-qRNQZttBTfYwLBnd .cluster span{color:#333;}#mermaid-svg-qRNQZttBTfYwLBnd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qRNQZttBTfYwLBnd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qRNQZttBTfYwLBnd rect.text{fill:none;stroke-width:0;}#mermaid-svg-qRNQZttBTfYwLBnd .icon-shape,#mermaid-svg-qRNQZttBTfYwLBnd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qRNQZttBTfYwLBnd .icon-shape p,#mermaid-svg-qRNQZttBTfYwLBnd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qRNQZttBTfYwLBnd .icon-shape .label rect,#mermaid-svg-qRNQZttBTfYwLBnd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qRNQZttBTfYwLBnd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qRNQZttBTfYwLBnd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qRNQZttBTfYwLBnd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lesson14 中断
按键
8042 OBF=1
PIC IRQ1
CPU 查 IDT 向量33
irq1_handler 读0x60入缓冲
EOI → STI 后 HLT 唤醒
read_char 从缓冲取键
lesson13 轮询
IN 0x64
否 空转

read_char
OBF=1?
IN 0x60


二、IDT:中断来了跳去哪

1. 为什么需要 IDT

在实模式下,中断发生时 CPU 会去内存前 1KB 的 IVT(中断向量表) 寻找 4 字节的 CS:IP。但进入保护模式后,IVT 宣告失效,取而代之的是 IDT(Interrupt Descriptor Table)。

中断门描述符(Interrupt Gate)的硬件细节

IDT 数组中存放的每一项都是一个 8 字节(64位) 的门描述符。其硬件内存布局如下:

bash 复制代码
31                16 15  14 13 12  11  8 7    5 4        0
+---------------------+---+-----+---+-----+---+---+--------+
|   Offset 31:16      | P | DPL | 0 | Type| 0 | 0 |Reserved| 高 32 位
+---------------------+---+-----+---+-----+---+---+--------+
|   Segment Selector  |           Offset 15:0              | 低 32 位
+---------------------+------------------------------------+
  • Offset (31:0):中断处理程序(ISR)在内存中的 32 位绝对物理地址(分为高 16 位和低 16 位拼凑)。
  • Segment Selector (15:0):目标代码段的选择子。在平坦模型内核中,这里恒为内核代码段选择子 0x08(GDT 的第 1 项)。
  • Type (11:8):对于 32 位中断门,此字段固定为 0xE(即二进制 1110)。
  • DPL (14:13):特权级。内核中断设为 00。当发生硬件中断时,即使当前 CPU 处于用户态(DPL=3),也能无缝切入内核态执行。
  • P (15):有效位。置 1 表示该中断门有效。

门描述符里保存:

字段 含义
目标偏移 处理程序入口 EIP
段选择子 处理程序所在代码段(平坦模式下为 8
类型 0x8E = 32 位中断门(进入 ISR 时自动清 IF)

LIDT 加载 IDTR(基址 + 界限),与 LGDT 加载 GDT 类似。

2. 向量号布局

范围 用途
0 ~ 31 CPU 保留异常(#DE、#GP、#PF ...)
32 ~ 47 常见 OS 把 8259 PIC 的 IRQ0~15 映射到此
48 ~ 255 软件中断、其他设备

IRQ 与向量不是同一个概念: 键盘占 IRQ1 这条硬件线;经 PIC 重映射后,对应 中断向量 330x20 + 1)。8259A 的端口、ICW/OCW、IMR、EOI 等详见 第三节

3. 代码:注册 IRQ1 门描述符

utilities.s 中预留 256×8 字节的 idt,只填写向量 33 一项:

nasm 复制代码
SELECTOR_CODE equ 8
IRQ1_VECTOR   equ 33          ; PIC 重映射后 IRQ1 → 0x21

idt:      times 256 * 8 db 0
idt_ptr:
    dw idt_end - idt - 1
    dd idt

init_idt:
    MOV  EAX, irq1_handler
    MOV  WORD [idt + IRQ1_VECTOR * 8 + 0], AX      ; 偏移低 16 位
    MOV  WORD [idt + IRQ1_VECTOR * 8 + 2], SELECTOR_CODE
    MOV  BYTE [idt + IRQ1_VECTOR * 8 + 5], 0x8E  ; 32 位中断门
    SHR  EAX, 16
    MOV  WORD [idt + IRQ1_VECTOR * 8 + 6], AX      ; 偏移高 16 位
    LIDT [idt_ptr]
    RET

pm.inc 中的 Gate 可静态生成门描述符;本章仅注册一项,直接在内存里写字段更直观。


三、8259A 可编程中断控制器(PIC)

1. 8259A 是什么

8259A (Programmable Interrupt Controller,可编程中断控制器)是 IBM PC/AT 时代的经典芯片,用来管理 外部设备中断

CPU 只有一条 INTR 中断请求输入线,外设却有键盘、时钟、磁盘、串口等多路信号。8259A 的作用就是:

  1. 汇聚 多条 IRQ 线;
  2. 优先级 决定先响应哪一路;
  3. 向 CPU 拉 INTR;
  4. CPU 响应后,通过 INTA 周期告知 IRQ 编号
  5. OS 把 IRQ 编号换算成 中断向量号,再查 IDT 跳转。

#mermaid-svg-ivjDLQUHTYEdX3TE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ivjDLQUHTYEdX3TE .error-icon{fill:#552222;}#mermaid-svg-ivjDLQUHTYEdX3TE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ivjDLQUHTYEdX3TE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ivjDLQUHTYEdX3TE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ivjDLQUHTYEdX3TE .marker.cross{stroke:#333333;}#mermaid-svg-ivjDLQUHTYEdX3TE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ivjDLQUHTYEdX3TE p{margin:0;}#mermaid-svg-ivjDLQUHTYEdX3TE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster-label text{fill:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster-label span{color:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster-label span p{background-color:transparent;}#mermaid-svg-ivjDLQUHTYEdX3TE .label text,#mermaid-svg-ivjDLQUHTYEdX3TE span{fill:#333;color:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE .node rect,#mermaid-svg-ivjDLQUHTYEdX3TE .node circle,#mermaid-svg-ivjDLQUHTYEdX3TE .node ellipse,#mermaid-svg-ivjDLQUHTYEdX3TE .node polygon,#mermaid-svg-ivjDLQUHTYEdX3TE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ivjDLQUHTYEdX3TE .rough-node .label text,#mermaid-svg-ivjDLQUHTYEdX3TE .node .label text,#mermaid-svg-ivjDLQUHTYEdX3TE .image-shape .label,#mermaid-svg-ivjDLQUHTYEdX3TE .icon-shape .label{text-anchor:middle;}#mermaid-svg-ivjDLQUHTYEdX3TE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ivjDLQUHTYEdX3TE .rough-node .label,#mermaid-svg-ivjDLQUHTYEdX3TE .node .label,#mermaid-svg-ivjDLQUHTYEdX3TE .image-shape .label,#mermaid-svg-ivjDLQUHTYEdX3TE .icon-shape .label{text-align:center;}#mermaid-svg-ivjDLQUHTYEdX3TE .node.clickable{cursor:pointer;}#mermaid-svg-ivjDLQUHTYEdX3TE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ivjDLQUHTYEdX3TE .arrowheadPath{fill:#333333;}#mermaid-svg-ivjDLQUHTYEdX3TE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ivjDLQUHTYEdX3TE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ivjDLQUHTYEdX3TE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ivjDLQUHTYEdX3TE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ivjDLQUHTYEdX3TE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ivjDLQUHTYEdX3TE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster text{fill:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE .cluster span{color:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ivjDLQUHTYEdX3TE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ivjDLQUHTYEdX3TE rect.text{fill:none;stroke-width:0;}#mermaid-svg-ivjDLQUHTYEdX3TE .icon-shape,#mermaid-svg-ivjDLQUHTYEdX3TE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ivjDLQUHTYEdX3TE .icon-shape p,#mermaid-svg-ivjDLQUHTYEdX3TE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ivjDLQUHTYEdX3TE .icon-shape .label rect,#mermaid-svg-ivjDLQUHTYEdX3TE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ivjDLQUHTYEdX3TE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ivjDLQUHTYEdX3TE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ivjDLQUHTYEdX3TE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} IRQ1
IRQ0
IRQ6
INTR
INTA
查 IDT
键盘 8042
8259A PIC
时钟 8253
软盘
x86 CPU
中断处理程序

历史说明: 现代多核 PC 已普遍改用 APIC / IOAPIC ,8259A 在物理机上多由芯片组模拟或仅用于兼容模式。但在 Bochs、QEMU 教学环境以及《30天自制操作系统》等入门路线中,8259A 仍是理解 「外设 IRQ → 向量号 → IDT」 的最佳起点。


2. 主从级联:最多 15 路 IRQ

一片 8259A 有 8 条 IRQ 输入 (IRQ0~IRQ7)。PC/AT 通过 级联 再挂第二片,形成 主片 + 从片

芯片 I/O 端口 管理 IRQ
主片(Master) 命令 0x20 / 数据 0x21 IRQ0~7
从片(Slave) 命令 0xA0 / 数据 0xA1 IRQ8~15

从片的 INTR 输出接到 主片的 IRQ2 ,因此主片 IRQ2 被占用为「级联线」,实际可用 15 路 外设中断(IRQ01、37、8~15)。
#mermaid-svg-1SIVQVyf2aj5iWix{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1SIVQVyf2aj5iWix .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1SIVQVyf2aj5iWix .error-icon{fill:#552222;}#mermaid-svg-1SIVQVyf2aj5iWix .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1SIVQVyf2aj5iWix .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1SIVQVyf2aj5iWix .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1SIVQVyf2aj5iWix .marker.cross{stroke:#333333;}#mermaid-svg-1SIVQVyf2aj5iWix svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1SIVQVyf2aj5iWix p{margin:0;}#mermaid-svg-1SIVQVyf2aj5iWix .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1SIVQVyf2aj5iWix .cluster-label text{fill:#333;}#mermaid-svg-1SIVQVyf2aj5iWix .cluster-label span{color:#333;}#mermaid-svg-1SIVQVyf2aj5iWix .cluster-label span p{background-color:transparent;}#mermaid-svg-1SIVQVyf2aj5iWix .label text,#mermaid-svg-1SIVQVyf2aj5iWix span{fill:#333;color:#333;}#mermaid-svg-1SIVQVyf2aj5iWix .node rect,#mermaid-svg-1SIVQVyf2aj5iWix .node circle,#mermaid-svg-1SIVQVyf2aj5iWix .node ellipse,#mermaid-svg-1SIVQVyf2aj5iWix .node polygon,#mermaid-svg-1SIVQVyf2aj5iWix .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1SIVQVyf2aj5iWix .rough-node .label text,#mermaid-svg-1SIVQVyf2aj5iWix .node .label text,#mermaid-svg-1SIVQVyf2aj5iWix .image-shape .label,#mermaid-svg-1SIVQVyf2aj5iWix .icon-shape .label{text-anchor:middle;}#mermaid-svg-1SIVQVyf2aj5iWix .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1SIVQVyf2aj5iWix .rough-node .label,#mermaid-svg-1SIVQVyf2aj5iWix .node .label,#mermaid-svg-1SIVQVyf2aj5iWix .image-shape .label,#mermaid-svg-1SIVQVyf2aj5iWix .icon-shape .label{text-align:center;}#mermaid-svg-1SIVQVyf2aj5iWix .node.clickable{cursor:pointer;}#mermaid-svg-1SIVQVyf2aj5iWix .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1SIVQVyf2aj5iWix .arrowheadPath{fill:#333333;}#mermaid-svg-1SIVQVyf2aj5iWix .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1SIVQVyf2aj5iWix .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1SIVQVyf2aj5iWix .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1SIVQVyf2aj5iWix .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1SIVQVyf2aj5iWix .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1SIVQVyf2aj5iWix .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1SIVQVyf2aj5iWix .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1SIVQVyf2aj5iWix .cluster text{fill:#333;}#mermaid-svg-1SIVQVyf2aj5iWix .cluster span{color:#333;}#mermaid-svg-1SIVQVyf2aj5iWix div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1SIVQVyf2aj5iWix .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1SIVQVyf2aj5iWix rect.text{fill:none;stroke-width:0;}#mermaid-svg-1SIVQVyf2aj5iWix .icon-shape,#mermaid-svg-1SIVQVyf2aj5iWix .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1SIVQVyf2aj5iWix .icon-shape p,#mermaid-svg-1SIVQVyf2aj5iWix .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1SIVQVyf2aj5iWix .icon-shape .label rect,#mermaid-svg-1SIVQVyf2aj5iWix .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1SIVQVyf2aj5iWix .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1SIVQVyf2aj5iWix .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1SIVQVyf2aj5iWix :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} INTR 接主片 IRQ2
从片 8259A 端口 A0h/A1h
IRQ8 RTC
IRQ9 ...
IRQ15 ...
主片 8259A 端口 20h/21h
IRQ0 时钟
IRQ1 键盘
IRQ2 级联从片
IRQ3 串口2
IRQ4 串口1
IRQ5 并口2
IRQ6 软盘
IRQ7 并口1
CPU INTR

本章键盘走主片 IRQ1 ,EOI 只需写主片命令端口 0x20


3. IRQ 线 vs 中断向量号

初学者容易混淆的两个概念:

概念 含义 键盘例子
IRQ 线 硬件请求线编号(8259A 输入脚) IRQ1
中断向量号 CPU 查 IDT 用的下标(0~255) 由 PIC 配置决定

8259A 只决定「哪条 IRQ 线对应哪个向量号」;CPU 拿到向量号后再查 IDT。因此 PIC 映射IDT 注册 必须一致。


3.1 为什么必须「重映射」(本章最核心)

这是本章最容易让初学者遭遇 Triple Fault(三重故障 → 重启) 的知识点。

致命隐患:向量号冲突

IBM PC 在设计之初留下了一个历史包袱:实模式下,BIOS 默认把 8259A 主片 IRQ0~7 映射到中断向量 8~15

然而 Intel 在设计 80386 保护模式时规定:向量 0~31 强制保留给 CPU 内部异常(如 0 为除零、13 为 #GP、14 为 #PF)。

硬件引脚 BIOS 默认向量 保护模式下 CPU 的理解 灾难后果
IRQ0(时钟) 0x08 #DF(Double Fault 双重故障) 时钟一触发,CPU 误以为发生双重故障,直接崩溃重启
IRQ1(键盘) 0x09 协处理器段超越(历史保留异常) 敲击键盘也会撞上异常区;IDT 空门 → #GP → #DF → Triple Fault
IRQ2(级联) 0x0A #TS(Invalid TSS) 从片相关中断同样踩雷
... 11~15 #NP / #SS / #GP / #PF / 保留 任意未屏蔽 IRQ 都可能引爆

BIOS 默认映射一览:

IRQ 默认向量号
IRQ0 8
IRQ1 9
IRQ2 10
... ...
IRQ7 15
IRQ8 16
... ...
IRQ15 23

8253 PIT 以约 18.2 Hz 周期拉高 IRQ0。若不重映射就 STI,时钟中断会以向量 8 进入 IDT------而这正是 #DF 的槽位。空门描述符 → #GP → 再 #DF → Triple Fault 重启。键盘 IRQ1→9 同理。

解药:ICW 四步握手,整体后移到 32~47

为避开 0~31 的雷区,必须向主片端口(0x20/0x21)和从片端口(0xA0/0xA1)按固定顺序发送 ICW(Initialization Command Words) ,把外部中断整体映射到 向量 32~47

步骤 命令字 本章典型值 作用
1 ICW1 0x110x20/0xA0 启动初始化,级联模式,需要 ICW4
2 ICW2 主片 0x20、从片 0x28 向量基址:IRQ0→32,IRQ8→40
3 ICW3 主片 0x04、从片 0x02 主片 IRQ2 接从片,从片编号 2
4 ICW4 0x01 8086 模式(x86 必备)
之后 IMR 主片 0xFD、从片 0xFF 仅开放 IRQ1,其余全屏蔽

重映射后的布局(本章做法,基址 0x20):

IRQ 新向量号 典型设备
IRQ0 32 8253 PIT Channel 0 (系统 tick,见 [第四节](#IRQ 新向量号 典型设备 IRQ0 32 8253 PIT Channel 0(系统 tick,见 第四节;本章 IMR 屏蔽) IRQ1 33 8042 键盘(本章开放,IDT[33] 已注册) IRQ2 34 级联从片 … … … IRQ15 47 各类外设);本章 IMR 屏蔽)
IRQ1 33 8042 键盘(本章开放,IDT33 已注册)
IRQ2 34 级联从片
... ... ...
IRQ15 47 各类外设
向量范围 用途
0 ~ 31 CPU 保留异常(#DE、#DF、#GP、#PF 等)------外设 IRQ 绝不能落在这里
32 ~ 47 8259A 重映射后的 IRQ0~15
48 ~ 255 软件中断 / 其他设备

结论: 任何进入保护模式的 OS,都必须在 STI 之前 对 8259A 执行完整的 ICW1~4 重映射。本章 PIC_REMAP equ 1,详细位域与代码见下文 §5§6;踩坑记录见 第十节


4. 端口与寄存器组

8259A 通过 4 个 I/O 端口 访问,每个端口读写含义由 命令字 决定:

端口
0x20 主片 命令(OCW/ICW 首字节) 主片 ISR/IRR(需 OCW3)
0x21 主片 数据(ICW2~4、IMR) 主片 IMR
0xA0 从片 命令 从片 ISR/IRR
0xA1 从片 数据 从片 IMR

4.1 两类控制字

在微机原理与接口技术(特别是可编程中断控制器 8259A)中,在系统启动或复位后,要对 8259A 芯片的工作模式进行一次性配置,建立其基础的工作框架,即ICW,全称是 Initialization Command Words,中文称为初始化命令字。

ICW 的核心功能与分类

8259A 共有 4 个初始化命令字(ICW1 ~ ICW4),它们需要严格按照顺序写入,分别负责不同的基础配置:

  • ICW1(基本功能设置):决定中断请求的触发方式(边沿触发或电平触发)、设置是单片工作还是多片级联模式,并指示是否需要写入 ICW4。
  • ICW2(中断向量设置):用于设置中断向量基地址(中断类型码的起始地址)。CPU 收到中断后,会根据这个基地址和中断引脚的编号找到对应的中断服务程序。
  • ICW3(级联控制字):仅在多片 8259A 级联时使用。主片用它来标识哪些 IRQ 引脚连接了从片;从片用它来标识自己在级联系统中的编号。
  • ICW4(高级方式控制):用于配置更高级的系统行为,例如选择 CPU 系统模式(如 8086 模式)、设置中断结束方式(如自动 EOI)、缓冲模式以及全嵌套方式等。
ICW 与 OCW 的区别

与 ICW 对应的是 OCW(Operation Command Words,操作命令字)。两者的主要区别在于:

  • ICW(初始化命令字):属于一次性配置。在系统上电或复位时按严格顺序写入,决定了芯片的底层基础架构,初始化完成后一般不再改动。
  • OCW(操作命令字):属于运行时控制。在芯片初始化完成后的正常工作期间,可以随时写入,用于动态控制中断行为,例如动态屏蔽/开放某个中断(OCW1)、发送中断结束命令 EOI(OCW2)或读取内部状态等。
    结合你上一轮提供的汇编代码,init_pic 函数中向 0x20 和 0x21 等端口连续写入的 0x11、0x20、0x28 等字节,正是严格按照顺序写入的 ICW1 到 ICW4,用于重新映射(Remap)主从 8259A 的中断向量基地址。
类型 名称 何时使用
ICW Initialization Command Word 初始化 时按 ICW1→ICW4 顺序写入
OCW Operation Command Word 运行中 屏蔽 IRQ、读状态、发 EOI 等

初始化完成后,再写 0x11 等 ICW 值会破坏芯片状态,须重新初始化。

4.2 内部关键寄存器(不必直接读,理解即可)

寄存器 全称 作用
IRR Interrupt Request Register 外设拉高了 IRQ 线,对应 bit 置 1
ISR In-Service Register CPU 已开始处理的中断,对应 bit 置 1
IMR Interrupt Mask Register 1 = 屏蔽 该 IRQ,0 = 允许

优先级(默认): IRQ0 最高,IRQ7 最低;从片 IRQ8~15 整体优先级介于主片 IRQ1 与 IRQ2 之间(具体与 ICW4 配置有关)。


5. 初始化命令字 ICW1 ~ ICW4

8259A 上电后必须按固定顺序初始化。本章 init_pic主从各写一遍

ICW1 --- 启动初始化(写命令端口)

0x20 / 0xA00x11

含义
D0 1 需要 ICW4
D1 1 级联模式(非单片)
D4 1 固定特征位,必须是 1

写 ICW1 后,芯片进入初始化状态,自动屏蔽所有 IRQ,并准备接收后续 ICW。

ICW2 --- 中断向量基址(写数据端口)

芯片 写入端口 本章值 含义
主片 0x21 0x20 IRQ0→32,IRQ1→33,...
从片 0xA1 0x28 IRQ8→40,IRQ9→41,...

写入的是 IRQ0 / IRQ8 对应的向量号,后续 IRQ 依次 +1。

ICW3 --- 级联关系(写数据端口)

芯片 本章值 含义
主片 0x04 bit2=1 → IRQ2 上接从片
从片 0x02 从片在中片级联链上的编号为 2

ICW4 --- 附加模式(写数据端口)

0x21 / 0xA10x01

含义
D0 1 8086/8088 模式(必须,AT 架构)

未设置此项时,EOI、向量格式可能与 x86 保护模式不兼容。

初始化后立刻设置 IMR

ICW 序列结束后,通过 IMR 决定哪些 IRQ 能进来。写 数据端口 0x21 / 0xA1

nasm 复制代码
    ; 主片:仅开放 IRQ1(键盘)
    MOV  AL, 0xFD          ; 二进制 11111101,bit1=0 → IRQ1 开放
    OUT  0x21, AL
    ; 从片:全部屏蔽
    MOV  AL, 0xFF
    OUT  0xA1, AL

IMR 位含义: bit N = 1 表示 屏蔽 IRQN0 表示 允许

bit1 IRQ1 键盘
0xFF 1 屏蔽
0xFD 0 开放

为何要屏蔽 IRQ0 等: 本章 IDT 只注册了向量 33。若 IRQ0(时钟)未屏蔽且触发,CPU 会跳到空的 IDT 项 → triple fault


6. 完整 init_pic 与 ICW 对照

nasm 复制代码
init_pic:
    ; ICW1
    MOV  AL, 0x11
    OUT  0x20, AL          ; 主片
    OUT  0xA0, AL          ; 从片

    ; ICW2:向量基址
    MOV  AL, 0x20
    OUT  0x21, AL          ; 主片 IRQ0 → 32
    MOV  AL, 0x28
    OUT  0xA1, AL          ; 从片 IRQ8 → 40

    ; ICW3:级联
    MOV  AL, 0x04
    OUT  0x21, AL          ; 主片 IRQ2 接从片
    MOV  AL, 0x02
    OUT  0xA1, AL          ; 从片编号 2

    ; ICW4:8086 模式
    MOV  AL, 0x01
    OUT  0x21, AL
    OUT  0xA1, AL

    ; IMR:仅开放 IRQ1
    MOV  AL, 0xFD
    OUT  0x21, AL
    MOV  AL, 0xFF
    OUT  0xA1, AL
    RET

7. 一次键盘中断的完整流程

irq1_handler IDT CPU 8259A 主片 8042 按键 irq1_handler IDT CPU 8259A 主片 8042 按键 #mermaid-svg-a97I8jZoHg08ELmf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a97I8jZoHg08ELmf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a97I8jZoHg08ELmf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a97I8jZoHg08ELmf .error-icon{fill:#552222;}#mermaid-svg-a97I8jZoHg08ELmf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a97I8jZoHg08ELmf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a97I8jZoHg08ELmf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a97I8jZoHg08ELmf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a97I8jZoHg08ELmf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a97I8jZoHg08ELmf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a97I8jZoHg08ELmf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a97I8jZoHg08ELmf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a97I8jZoHg08ELmf .marker.cross{stroke:#333333;}#mermaid-svg-a97I8jZoHg08ELmf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a97I8jZoHg08ELmf p{margin:0;}#mermaid-svg-a97I8jZoHg08ELmf .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a97I8jZoHg08ELmf text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-a97I8jZoHg08ELmf .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-a97I8jZoHg08ELmf .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-a97I8jZoHg08ELmf .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-a97I8jZoHg08ELmf .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-a97I8jZoHg08ELmf #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-a97I8jZoHg08ELmf .sequenceNumber{fill:white;}#mermaid-svg-a97I8jZoHg08ELmf #sequencenumber{fill:#333;}#mermaid-svg-a97I8jZoHg08ELmf #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-a97I8jZoHg08ELmf .messageText{fill:#333;stroke:none;}#mermaid-svg-a97I8jZoHg08ELmf .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a97I8jZoHg08ELmf .labelText,#mermaid-svg-a97I8jZoHg08ELmf .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-a97I8jZoHg08ELmf .loopText,#mermaid-svg-a97I8jZoHg08ELmf .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-a97I8jZoHg08ELmf .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-a97I8jZoHg08ELmf .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-a97I8jZoHg08ELmf .noteText,#mermaid-svg-a97I8jZoHg08ELmf .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-a97I8jZoHg08ELmf .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a97I8jZoHg08ELmf .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a97I8jZoHg08ELmf .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-a97I8jZoHg08ELmf .actorPopupMenu{position:absolute;}#mermaid-svg-a97I8jZoHg08ELmf .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-a97I8jZoHg08ELmf .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-a97I8jZoHg08ELmf .actor-man circle,#mermaid-svg-a97I8jZoHg08ELmf line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-a97I8jZoHg08ELmf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} IF=1 且 STI 已执行 扫描码 OBF=1, 拉 IRQ1 IRQ1 有效 IRR bit1=1 INTR=1 INTA 周期 向量号 33 查门描述符 跳转 irq1_handler IN 0x60 读扫描码 OUT 0x20, 0x20 EOI IRET

  1. 8042 收到扫描码,置 OBF ,若命令字节 bit0=1 则向 PIC IRQ1 发请求。
  2. PIC 置 IRR 相应位,若未被 IMR 屏蔽且优先级足够,向 CPU 拉 INTR
  3. CPU 完成当前指令且 IF=1 ,响应中断,经 INTA 从 PIC 取得 向量 33
  4. CPU 查 IDT33 ,压栈 CS/EIP/EFLAGS,跳转 irq1_handler
  5. ISR 读 0x60 (必须读,否则 8042 不会发下一次中断),写环形缓冲,发 EOI
  6. IRET 恢复现场;若主线程在 HLT,此时被唤醒。

8. EOI(End Of Interrupt)

中断处理程序结束前,必须通知 PIC:当前这一级 IRQ 已服务完毕 ,否则 ISR 位一直置位,同级或更低优先级中断可能被阻塞。

非特定 EOI(本章使用)

nasm 复制代码
    MOV  AL, 0x20
    OUT  0x20, AL          ; 主片:结束优先级最高的那一条 in-service 中断

键盘 IRQ1 接 主片 ,写 0x20 命令端口 即可。

特定 EOI(了解)

若需明确结束某一条 IRQ,可写 0x20 | IRQ号 到主/从片命令端口,例如结束 IRQ1:OUT 0x20, 0x21。多 IRQ 并发、嵌套中断时才常用;本章单 IRQ 场景用 0x20 足够。

从片 IRQ 的 EOI

若中断来自 从片 (IRQ8~15),须 向从片发 EOI, 向主片发 EOI(因为 IRQ2 级联)。键盘 IRQ1 在主片,无需0xA0


9. 运行期 OCW 操作(本章未用,供扩展)

操作 做法
屏蔽某 IRQ 读 IMR → 置对应 bit → 写回 0x21/0xA1
开放某 IRQ 读 IMR → 清对应 bit → 写回
读 IRR/ISR 先写 OCW3 到命令端口,再从命令端口读

例如开放 IRQ0 定时器(须先注册 IDT32 并初始化 PIT,见 [第四节 8253](#第四节 8253)):

nasm 复制代码
    IN   AL, 0x21
    AND  AL, 0xFE          ; 清 bit0,开放 IRQ0
    OUT  0x21, AL

10. BIOS 默认 vs 本章重映射

场景 IRQ1 向量 IDT 应注册 是否写 ICW
BIOS 实模式 / 未 remap 9 IDT9
本章保护模式 OS 33 IDT33

常见错误: 执行了 init_pic 重映射,IDT 却仍注册向量 9;或 重映射,IDT 注册 33。按键后 CPU 跳到 未注册的门 → triple fault 或「无反应」(视环境而定)。

调试时可对照 tools/bochsout.txt:若 IF=1 且 EIP 停在 HLT,多为 8042 未开中断位IMR 仍屏蔽 IRQ1 ;若按键立刻重启,优先查 向量号与 IDT 是否一致


四、8253 可编程间隔定时器(PIT)

1. 8253 是什么

8253 (Programmable Interval Timer,可编程间隔定时器)是 PC 上负责 硬件计时 的经典芯片;后续 8254 与其 pin 兼容、功能相近,BIOS/OS 文档里常统称 PIT

它内部有 3 个独立的 16 位计数器 (通道 Channel 0~2),对固定 输入时钟 分频,在计数到设定值时改变输出电平,或向 8259A PIC 的 IRQ0 发中断请求。
#mermaid-svg-QuBLsb6x4aOZjZI2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QuBLsb6x4aOZjZI2 .error-icon{fill:#552222;}#mermaid-svg-QuBLsb6x4aOZjZI2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QuBLsb6x4aOZjZI2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .marker.cross{stroke:#333333;}#mermaid-svg-QuBLsb6x4aOZjZI2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QuBLsb6x4aOZjZI2 p{margin:0;}#mermaid-svg-QuBLsb6x4aOZjZI2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster-label text{fill:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster-label span{color:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster-label span p{background-color:transparent;}#mermaid-svg-QuBLsb6x4aOZjZI2 .label text,#mermaid-svg-QuBLsb6x4aOZjZI2 span{fill:#333;color:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .node rect,#mermaid-svg-QuBLsb6x4aOZjZI2 .node circle,#mermaid-svg-QuBLsb6x4aOZjZI2 .node ellipse,#mermaid-svg-QuBLsb6x4aOZjZI2 .node polygon,#mermaid-svg-QuBLsb6x4aOZjZI2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .rough-node .label text,#mermaid-svg-QuBLsb6x4aOZjZI2 .node .label text,#mermaid-svg-QuBLsb6x4aOZjZI2 .image-shape .label,#mermaid-svg-QuBLsb6x4aOZjZI2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-QuBLsb6x4aOZjZI2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .rough-node .label,#mermaid-svg-QuBLsb6x4aOZjZI2 .node .label,#mermaid-svg-QuBLsb6x4aOZjZI2 .image-shape .label,#mermaid-svg-QuBLsb6x4aOZjZI2 .icon-shape .label{text-align:center;}#mermaid-svg-QuBLsb6x4aOZjZI2 .node.clickable{cursor:pointer;}#mermaid-svg-QuBLsb6x4aOZjZI2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .arrowheadPath{fill:#333333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QuBLsb6x4aOZjZI2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QuBLsb6x4aOZjZI2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QuBLsb6x4aOZjZI2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster text{fill:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 .cluster span{color:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QuBLsb6x4aOZjZI2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QuBLsb6x4aOZjZI2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-QuBLsb6x4aOZjZI2 .icon-shape,#mermaid-svg-QuBLsb6x4aOZjZI2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QuBLsb6x4aOZjZI2 .icon-shape p,#mermaid-svg-QuBLsb6x4aOZjZI2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QuBLsb6x4aOZjZI2 .icon-shape .label rect,#mermaid-svg-QuBLsb6x4aOZjZI2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QuBLsb6x4aOZjZI2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QuBLsb6x4aOZjZI2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QuBLsb6x4aOZjZI2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Channel 0
Channel 1
Channel 2
输入时钟 1.193182 MHz
8253 PIT
PIC IRQ0
DRAM 刷新 历史用途
PC 扬声器 Gate
8259A
CPU 定时中断

与 8259A 的关系: 8253 不产生 CPU 向量 ,只拉 IRQ0 线 ;向量号仍由 PIC 决定(BIOS 默认 8 ,重映射后 32 )。下一章做 定时器中断 / sleep / 调度 时,通常同时配置 PIT + IDT32 + 开放 PIC IRQ0

本章为何屏蔽 IRQ0: init_pic 写 IMR 0xFD 屏蔽 IRQ0,因为尚未注册 向量 32 的处理程序;若开放,约 18.2 次/秒 的 IRQ0 会触发 triple fault。


2. 输入时钟与 3 个通道

2.1 输入频率

PC 上 PIT 输入时钟固定为:

f in = 1   193   182    Hz ≈ 1193182    Hz f_{\text{in}} = 1\,193\,182 \;\text{Hz} \approx 1193182 \;\text{Hz} fin=1193182Hz≈1193182Hz

(即 ISA 总线时钟 14.31818 MHz12 分频。)

输出频率:

f out = 1193182 分频值 N f_{\text{out}} = \frac{1193182}{\text{分频值 }N} fout=分频值 N1193182

分频值 N 为 16 位,范围 1 ~ 65535 。例如 N = 65535 时:

f ≈ 1193182 65535 ≈ 18.2    Hz f \approx \frac{1193182}{65535} \approx 18.2 \;\text{Hz} f≈655351193182≈18.2Hz

约每 55ms 中断一次 ------BIOS 默认 Channel 0 常用此量级,也是 DOS 时代 INT 08h 系统时钟 tick 的来源。

2.2 三个通道在 PC 上的用途

通道 I/O 数据端口 PC/AT 典型用途 本章
0 0x40 系统定时器 → PIC IRQ0 被 IMR 屏蔽
1 0x41 历史:DRAM 刷新(现代几乎不用) 未使用
2 0x42 PC 扬声器(与 0x61 bit0 Gate 配合) 未使用

#mermaid-svg-4p7RKrlYiVXeY5Pk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4p7RKrlYiVXeY5Pk .error-icon{fill:#552222;}#mermaid-svg-4p7RKrlYiVXeY5Pk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4p7RKrlYiVXeY5Pk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .marker.cross{stroke:#333333;}#mermaid-svg-4p7RKrlYiVXeY5Pk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4p7RKrlYiVXeY5Pk p{margin:0;}#mermaid-svg-4p7RKrlYiVXeY5Pk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster-label text{fill:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster-label span{color:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster-label span p{background-color:transparent;}#mermaid-svg-4p7RKrlYiVXeY5Pk .label text,#mermaid-svg-4p7RKrlYiVXeY5Pk span{fill:#333;color:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .node rect,#mermaid-svg-4p7RKrlYiVXeY5Pk .node circle,#mermaid-svg-4p7RKrlYiVXeY5Pk .node ellipse,#mermaid-svg-4p7RKrlYiVXeY5Pk .node polygon,#mermaid-svg-4p7RKrlYiVXeY5Pk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .rough-node .label text,#mermaid-svg-4p7RKrlYiVXeY5Pk .node .label text,#mermaid-svg-4p7RKrlYiVXeY5Pk .image-shape .label,#mermaid-svg-4p7RKrlYiVXeY5Pk .icon-shape .label{text-anchor:middle;}#mermaid-svg-4p7RKrlYiVXeY5Pk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .rough-node .label,#mermaid-svg-4p7RKrlYiVXeY5Pk .node .label,#mermaid-svg-4p7RKrlYiVXeY5Pk .image-shape .label,#mermaid-svg-4p7RKrlYiVXeY5Pk .icon-shape .label{text-align:center;}#mermaid-svg-4p7RKrlYiVXeY5Pk .node.clickable{cursor:pointer;}#mermaid-svg-4p7RKrlYiVXeY5Pk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .arrowheadPath{fill:#333333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4p7RKrlYiVXeY5Pk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4p7RKrlYiVXeY5Pk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4p7RKrlYiVXeY5Pk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster text{fill:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk .cluster span{color:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4p7RKrlYiVXeY5Pk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4p7RKrlYiVXeY5Pk rect.text{fill:none;stroke-width:0;}#mermaid-svg-4p7RKrlYiVXeY5Pk .icon-shape,#mermaid-svg-4p7RKrlYiVXeY5Pk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4p7RKrlYiVXeY5Pk .icon-shape p,#mermaid-svg-4p7RKrlYiVXeY5Pk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4p7RKrlYiVXeY5Pk .icon-shape .label rect,#mermaid-svg-4p7RKrlYiVXeY5Pk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4p7RKrlYiVXeY5Pk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4p7RKrlYiVXeY5Pk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4p7RKrlYiVXeY5Pk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 8253 三通道
OUT
OUT
OUT
Channel 0 系统时钟
Channel 1 刷新
Channel 2 扬声器
8259A IRQ0
内存刷新电路
0x61 bit0 Gate
扬声器


3. I/O 端口

端口 方向 含义
0x40 读/写 Channel 0 计数寄存器
0x41 读/写 Channel 1 计数寄存器
0x42 读/写 Channel 2 计数寄存器
0x43 控制字(选择通道、模式、读/写方式)

注意: 三个数据端口 共用 一套「先写控制字、再写分频值」的协议;必须先向 0x43 写入 控制字 ,再向 0x40/0x41/0x42 写入 16 位分频值(低字节、高字节顺序由控制字决定)。


4. 控制字(Control Word)格式

0x43 写入 1 字节 控制字,格式如下:

复制代码
  7   6   5   4   3   2   1   0
┌───┬───┬───────┬───────────────┐
│ SC│ RW│  MODE │     BCD       │
└───┴───┴───────┴───────────────┘
字段 含义
SC 7~6 选择通道:00=Ch0,01=Ch1,10=Ch2,11=读回命令
RW 5~4 读/写方式:00=锁存,01=仅低字节,10=仅高字节,11=先低后高
MODE 3~1 工作模式 0~5
BCD 0 0=二进制计数,1=BCD(PC 上恒用 0

示例: 配置 Channel 0、先写低字节再写高字节、Mode 3、二进制:

SC=00 , RW=11 , MODE=011 , BCD=0 \text{SC=00},\ \text{RW=11},\ \text{MODE=011},\ \text{BCD=0} SC=00, RW=11, MODE=011, BCD=0

⇒ 0 x 0011011 2 = 0 x 36 \Rightarrow 0x0011011_2 = \mathbf{0x36} ⇒0x00110112=0x36


5. 六种工作模式(MODE 0~5)

模式 名称 典型用途
0 中断结束计数 一次性定时,到时输出高电平
1 硬件可重触发单稳 脉冲宽度由计数值决定
2 速率发生器 周期性分频,计数到 1 时输出一个周期低脉冲
3 方波发生器 Channel 0 默认:对称方波,占空比约 50%
4 软件触发选通 写入计数值后启动
5 硬件触发选通 外部 Gate 触发

Channel 0 系统时钟 几乎总是 Mode 3(方波) :输出在 0/1 间周期性翻转,每个周期可在 OUT 下降沿 触发 IRQ0(具体边沿与模式有关;BIOS 默认配置下约 18.2 Hz 进 IRQ0)。

Mode 2(速率发生器)也常见于 OS 自行初始化 PIT 时(如 Linux 早期 tick)。


6. 初始化 Channel 0 示例(下一章参考)

下列代码 本章未使用 ;演示如何把 PIT 设为 约 100 Hz,并配合 PIC/IDT 开放 IRQ0:

nasm 复制代码
; 1193182 / 100 ≈ 11932
PIT_HZ      equ 100
PIT_DIV     equ 1193182 / PIT_HZ

init_pit_ch0:
    ; 控制字:Ch0, 先低后高, Mode 3, 二进制 → 0x36
    MOV  AL, 0x36
    OUT  0x43, AL

    MOV  AX, PIT_DIV
    OUT  0x40, AL          ; 低 8 位
    MOV  AL, AH
    OUT  0x40, AL          ; 高 8 位
    RET

配套步骤(下一章):

  1. IDT 注册 向量 32IRQ0_VECTOR equ 32
  2. init_pit_ch0 设置分频
  3. PIC IMR 清 bit0,开放 IRQ0:IN 0x21AND AL, 0xFEOUT 0x21, AL
  4. 定时器 ISR 内 tick++ ,发 EOI OUT 0x20, 0x20

7. 常用分频对照表

分频值 N 频率(约) 周期 典型场景
65535 18.2 Hz ~55 ms BIOS 默认 tick
11932 100 Hz 10 ms 简易内核 heartbeat
1193 1000 Hz 1 ms 较细粒度计时(开销大)
1193182 1 Hz 1 s 慢速演示

公式:N = 1193182 / 目标 Hz(整数除,会有微小误差)。


8. Channel 2 与 PC 扬声器(了解)

Channel 2 输出接 8255 PPI 端口 0x61 bit0(Gate),Gate=1 时方波驱动扬声器:

nasm 复制代码
    ; 设置 Ch2 频率(控制字 0xB6 = Ch2, 先低后高, Mode 3)
    MOV  AL, 0xB6
    OUT  0x43, AL
    ; 写入分频值到 0x42 ...
    ; 打开 Gate
    IN   AL, 0x61
    OR   AL, 3              ; bit0 Gate, bit1 数据(简化)
    OUT  0x61, AL

本章节未使用,与键盘中断无直接关系。


9. 一次 IRQ0 定时中断的流程

timer_handler CPU 8259A IRQ0 8253 Ch0 1.193182 MHz timer_handler CPU 8259A IRQ0 8253 Ch0 1.193182 MHz #mermaid-svg-momyx1WMmlMTxbIN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-momyx1WMmlMTxbIN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-momyx1WMmlMTxbIN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-momyx1WMmlMTxbIN .error-icon{fill:#552222;}#mermaid-svg-momyx1WMmlMTxbIN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-momyx1WMmlMTxbIN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-momyx1WMmlMTxbIN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-momyx1WMmlMTxbIN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-momyx1WMmlMTxbIN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-momyx1WMmlMTxbIN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-momyx1WMmlMTxbIN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-momyx1WMmlMTxbIN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-momyx1WMmlMTxbIN .marker.cross{stroke:#333333;}#mermaid-svg-momyx1WMmlMTxbIN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-momyx1WMmlMTxbIN p{margin:0;}#mermaid-svg-momyx1WMmlMTxbIN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-momyx1WMmlMTxbIN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-momyx1WMmlMTxbIN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-momyx1WMmlMTxbIN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-momyx1WMmlMTxbIN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-momyx1WMmlMTxbIN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-momyx1WMmlMTxbIN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-momyx1WMmlMTxbIN .sequenceNumber{fill:white;}#mermaid-svg-momyx1WMmlMTxbIN #sequencenumber{fill:#333;}#mermaid-svg-momyx1WMmlMTxbIN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-momyx1WMmlMTxbIN .messageText{fill:#333;stroke:none;}#mermaid-svg-momyx1WMmlMTxbIN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-momyx1WMmlMTxbIN .labelText,#mermaid-svg-momyx1WMmlMTxbIN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-momyx1WMmlMTxbIN .loopText,#mermaid-svg-momyx1WMmlMTxbIN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-momyx1WMmlMTxbIN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-momyx1WMmlMTxbIN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-momyx1WMmlMTxbIN .noteText,#mermaid-svg-momyx1WMmlMTxbIN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-momyx1WMmlMTxbIN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-momyx1WMmlMTxbIN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-momyx1WMmlMTxbIN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-momyx1WMmlMTxbIN .actorPopupMenu{position:absolute;}#mermaid-svg-momyx1WMmlMTxbIN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-momyx1WMmlMTxbIN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-momyx1WMmlMTxbIN .actor-man circle,#mermaid-svg-momyx1WMmlMTxbIN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-momyx1WMmlMTxbIN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 每个时钟递减计数 计数到 0,OUT 翻转 IRQ0 有效 INTR(若 IMR bit0=0) INTA 向量 32 IDT32 tick++, 任务调度等 OUT 0x20, 0x20 EOI IRET

IRQ1 键盘 对比:

对比项 IRQ0(8253) IRQ1(8042 键盘)
设备 PIT Channel 0 8042 键盘接口
数据端口 0x40~0x43 0x60/0x64
触发频率 周期性(如 18.2 Hz) 事件驱动(按键)
重映射后向量 32 33
本章 IMR 屏蔽 开放并处理
ISR 典型工作 tick++、调度 读扫描码入缓冲

10. 本章与 8253 的关系

项目 本章做法 原因
PIT 初始化 不改(沿用 BIOS 默认 Ch0) 只演示键盘中断
PIC IRQ0 屏蔽(IMR bit0=1) 无 IDT32,避免 triple fault
PIT 端口 不访问 避免破坏 BIOS 已设频率

BIOS 启动后 Channel 0 通常已在运行并 不断请求 IRQ0 ;本章用 0xFD 屏蔽 IRQ0 是刻意为之。下一章实现 定时器中断 时,再按 [第 6 节](#第 6 节) 初始化 PIT、注册 IDT32、开放 IMR bit0 即可。


五、8042 与键盘中断使能

与 lesson13 相同:0xAE 启用键盘 + 0xF4 开启扫描。不在 Bochs 里读写 8042 命令字节(易卡死或破坏键盘)。

注意: 不要使用 0xAD/0xA7/0xD2 那套「完整 OSDev 流程」------在 Bochs 里可能 卡死在 wait_8042

nasm 复制代码
init_8042:
    ; 0xAE → drain → 0xF4(kbd_cmd)→ drain
    RET

初始化顺序: CLIinit_pic(仅屏蔽 IRQ0)→ init_idt(向量 9 与 33)→ init_8042STI


六、IRQ1 处理程序与环形缓冲

1. 为何用缓冲

中断处理应尽量 :读硬件、存数据、EOI,复杂逻辑放到 read_char (主线程上下文)。扫描码先写入 环形队列 ,避免中断里调用 print_char 等可能重入的代码。

nasm 复制代码
KBD_RING_SIZE equ 64
kbd_ring: times KBD_RING_SIZE db 0
kbd_rpos: dd 0
kbd_wpos: dd 0

2. irq1_handler

nasm 复制代码
irq1_handler:
    PUSH EAX

    IN   AL, 0x60
    CALL kbd_ring_push

    MOV  AL, 0x20
    OUT  0x20, AL              ; EOI

    POP  EAX
    IRET

注意: ISR 必须用 IRET 返回;此处只保存/恢复 EAX 即可。

3. read_char:缓冲优先,端口兜底

nasm 复制代码
kbd_pop_scancode:
    MOV  EAX, [kbd_rpos]
    CMP  EAX, [kbd_wpos]
    JNE  .from_ring
    CALL read_kbd_scancode     ; 缓冲空:与 lesson13 相同,读 8042
    RET
.from_ring:
    ; 从 kbd_ring 取一字节
    RET

设计说明:

  • IRQ 正常时irq1_handler 先入队,kbd_pop 只消费缓冲,不读端口。
  • Bochs 实测 :IDT/PIC/IF 均正确时 IRQ1 仍可能进不了 handler,但 8042 OBF 有数据 ;缓冲空时 回退到 read_kbd_scancode,与 lesson13 一致,保证能打字。
  • 仍是 lesson14 架构(IDT + PIC + ISR + 环形缓冲),不是退回纯 lesson13。

read_char 仍采用:等按下 → 等释放 → 查 scancode_map


七、kernel.c

与 lesson13 相同,无需感知中断细节:

c 复制代码
void main(void)
{
    clear_screen();
    init_keyboard();    /* 内部:PIC + IDT + 8042 + STI */
    print_str("NotOneOS 32-bit Protected Mode.\r\nWelcome..\r\n", ...);
    print_str("> ", 2);

    char a;
    fin:
        a = read_char();
        if (a != 0) {
            print_char(a);
        }
        goto fin;
}

八、编译与运行

bash 复制代码
cd lesson14
make
make install
make run      # Bochs
make debug    # 调试

验证清单:

  • 欢迎语与 > 提示符正常
  • 软件光标 _ 可见
  • 点击 Bochs 窗口 获得焦点后,按键可回显
  • 调试器中 IF=1read_char 停在 HLT 而非 IN 0x64 轮询
  • 无 triple fault

调试建议(详见下方「按键无反应排查」):

复制代码
make debug
b 0xC200
c

地址以 ../target/kernel.map 为准(make 后自动生成)。


按键无反应排查

0. 你的日志说明什么

tools/bochsout.txt 末尾类似:

复制代码
IF=1
EIP=0000cc5f
>> call ... (0x0000cc33)

表示:欢迎语已打印,STI 已执行,CPU 在 read_charkbd_popHLT 里等键盘中断,不是 lesson13 那种 IN 0x64 轮询 。按键无反应 = IRQ1 没进来或 8042 没把扫描码送给 PIC

1. 先排除低级问题

  • 重新 make install 再运行。
  • 鼠标点击 Bochs 显示窗口(黑屏区域),不是下方调试命令行。
  • 确认能看到 Welcome..> 提示符。

2. 用断点定位「中断有没有触发」

重要: CPU 停在断点上时按键不会进中断 。须 bc 清断点或只保留 irq1_handler,再 c 继续运行后按键。

make 后打开 ../target/kernel.map(当前典型地址):

符号 典型地址 作用
irq1_handler 0xCC2F 按键应最先进这里
read_char 0xCC5D 主循环读键
init_keyboard 0xCBF6 末尾应已 STI
IDT9 0xC342 默认 PIC_REMAP=0 时查此项

推荐命令:

复制代码
bc
b 0xCC2F
c

点击 Display 窗口 ,按 a(调试器命令行不要抢焦点):

  • 能断在 irq1_handler → 中断通,查 scancode_map / 环形缓冲。
  • 断不到 → 继续第 4、5 步。

验证 IDT(PIC_REMAP=0 时查向量 9):

复制代码
xp /8bx 0xC342
info cpu

期望:06 2f cc 08 00 8e 00 00 一类(指向 0xCC2F),且 eflags 含 IF

3. 对照 lesson13(最快二分)

用 lesson13 编译运行:若 lesson13 能回显、lesson14 不能 → 8042 有数据,问题在 IDT/PIC/8042 命令字节 bit0 。若 两个都不行 → 先查 Bochs 焦点和键盘配置。

4. 查 PIC 与向量号(最常见根因)

代码里 utilities.s 顶部:

nasm 复制代码
PIC_REMAP equ 0    ; Bochs 默认:不重映射,IRQ1 → 向量 9
                   ; 改为 1 则 ICW 重映射,IRQ1 → 向量 33
PIC_REMAP IRQ1 向量 IDT 应注册 主片 IMR
0(Bochs 推荐) 9 IDT9 @ 0xC342 读改写:屏蔽 IRQ0、开放 IRQ1
1(真 OS 常用) 33 IDT33 @ 0xC402 0xFD

你之前的症状 (IDT33 正确、IF=1、按 a 不进 handler):多半是 Bochs 仍按 BIOS 发向量 9,或 8042 命令字节 bit0 未真正置 1 。当前默认已改为 PIC_REMAP=0 + 读命令字节前 drain_kbd

  • 按键后 立刻重启 → IDT 向量与 PIC 映射不一致(例如 remap=1 却只注册了 9)。
  • 不重启也不进 handler → 8042 未开 IRQ(查命令字节 bit0)或未点 Display 焦点。

5. 查 8042 是否有数据(中断以外的证据)

程序停在 HLT 时,若能在调试器里看到 0x64 的 bit0(OBF)随按键变 1 ,说明扫描码已到 8042,只是 没产生 IRQ → 重点查 init_8042 里命令字节 bit0 是否为 1

6. 现象对照

现象 方向
IF=1,EIP 在 kbd_pop/HLT 等中断;查 8042 bit0、PIC IMR、窗口焦点
按键即重启 IDT 向量号与 PIC 映射不一致
进了 irq1_handler 无字符 查表 / make-break 逻辑
lesson13 行、lesson14 不行 中断路径问题

九、常见问题

现象 可能原因
triple fault 启动即崩溃 IDT 未注册却 STI;或 IRQ0(8253) 未屏蔽且无 IDT32
有欢迎语但按键无反应 未点击 Bochs 窗口;8042 未开扫描/未开中断位
中断进错向量 PIC 未重映射却用向量 9;或重映射了却用向量 9
只响应一次键 忘记 EOI
乱码 scancode_map 偏移错误(见 lesson13 说明)

向量号对照:

配置 IRQ1 对应向量
BIOS 默认(不重映射 PIC) 9
本章 PIC 重映射到 0x20 33

本章采用 重映射 + 向量 33,与主流教程(《30天自制操作系统》等)一致。


十、PIC_REMAP 必须为 1:踩坑与修复

理论背景(向量冲突、Triple Fault、ICW 四步握手)已写入 [第三节 §3.1](#第三节 §3.1)。本节只保留实践结论。

1. 问题现象

PIC_REMAP equ 0(沿用 BIOS 默认 IRQ0~7 → 向量 8~15),在 Bochs 中常见:按键无反应,且很快自动重启(triple fault)

2. 根因(一句话)

BIOS 映射与 CPU 异常区 0~31 重叠;PIT 的 IRQ0 以向量 8(#DF)进来,空 IDT 门 → #GP → #DF → Triple Fault。即使勉强屏蔽 IRQ0,不写完整 ICW 也无法可靠开放 IRQ1、配置从片与 8086 模式。

3. 修复

utilities.s 中改为:

nasm 复制代码
PIC_REMAP     equ 1    ; 必须为 1:保护模式下必须重映射 8259A

init_pic 走完整 ICW1~4(主片基址 0x20、从片 0x28)+ IMR 0xFD/0xFF,IRQ1 → 向量 33 ,与 IDT[33] 一致。对照表见 §3.1§6

4. 教训总结

要点 说明
保护模式必须重映射 PIC BIOS 默认 8~15 与异常重叠,IRQ0 一触发即 triple fault
IMR 必须显式设置 0xFD 精确只开放 IRQ1,不能赌 BIOS 遗留值
IDT 向量号必须与 PIC 映射一致 重映射后 IRQ1→33;不一致则按键重启或无反应
ICW4 的 8086 模式位不可省略 不写 ICW4 则 EOI / 向量格式可能与 x86 不兼容

十一、本章小结

  • IDT 定义「向量号 → 处理函数」;8259A PIC 定义「IRQ 线 → 向量号」,并通过 IMR 屏蔽 / EOI 结束 管理中断生命周期
  • 8253 PIT1.193182 MHz 分频驱动 IRQ0 ;BIOS 默认约 18.2 Hz ;本章 屏蔽 IRQ0 ,下一章再初始化 Channel 0 并注册 IDT32
  • 8259A 主从级联共 15 路 IRQ;键盘占 主片 IRQ1 ,重映射后对应 向量 33
  • 键盘中断链路:按键 → 8042 → PIC IRQ1 → CPU → IDT33 → irq1_handler → EOI
  • 环形缓冲 + HLT 把「硬件响应」与「字符处理」解耦,CPU 不再空转
  • lesson13 的显存输出、软件光标、扫描码表可原样复用

后面章节可在此基础上增加:8253 定时器 IRQ0 (tick / sleep)、Shift 大写 、或 分页(CR3 + 页表)

源码:

https://gitee.com/xundh/learn-os/tree/master/lesson14

相关推荐
齊家治國平天下11 小时前
Android 14 Vehicle HAL (VHAL) 设计与实现深度解析
android·aaos·carservice·车载开发·android 14·vhal·vehicle hal
尊治12 小时前
变频器学习入门教程
学习·esim电工仿真·电工仿真软件·电工仿真·电工接线学习·esim电工制图
tyqtyq2212 小时前
旅行打包清单 App — HarmonyOS AI 应用开发技术博客
人工智能·学习·华为·生活·harmonyos
辞旧 lekkk13 小时前
【Redis初阶】常见数据类型
开发语言·数据库·c++·redis·学习·缓存·bootstrap
小弥儿14 小时前
GitHub今日热榜 | 2026-07-17:教育Agent与极低量化分庭抗礼
学习·开源·github
dtq042415 小时前
C语言-结构体详解
c语言·开发语言·学习
天选之子12316 小时前
DuckDB学习
jvm·学习
一只小菜鸡..17 小时前
Stanford CS144 学习笔记 (四):网络拥塞控制与 AIMD 算法
网络·笔记·学习
AFinalStone17 小时前
Android 7系统网络(八)应用API层—ConnectivityManager使用与实战调试
android·网络