操作系统学习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 为什么必须「重映射」(本章最核心))
-
- 致命隐患:向量号冲突
- [解药:ICW 四步握手,整体后移到 32~47](#解药:ICW 四步握手,整体后移到 32~47)
- [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_char用HLT休眠 等待按键,不再占用 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 重映射后,对应 中断向量 33 (0x20 + 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 的作用就是:
- 汇聚 多条 IRQ 线;
- 按 优先级 决定先响应哪一路;
- 向 CPU 拉 INTR;
- CPU 响应后,通过 INTA 周期告知 IRQ 编号;
- 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 | 0x11 → 0x20/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 / 0xA0 写 0x11:
| 位 | 值 | 含义 |
|---|---|---|
| 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 / 0xA1 写 0x01:
| 位 | 值 | 含义 |
|---|---|---|
| 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 表示 屏蔽 IRQN ;0 表示 允许。
| 值 | 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
- 8042 收到扫描码,置 OBF ,若命令字节 bit0=1 则向 PIC IRQ1 发请求。
- PIC 置 IRR 相应位,若未被 IMR 屏蔽且优先级足够,向 CPU 拉 INTR。
- CPU 完成当前指令且 IF=1 ,响应中断,经 INTA 从 PIC 取得 向量 33。
- CPU 查 IDT33 ,压栈 CS/EIP/EFLAGS,跳转
irq1_handler。 - ISR 读
0x60(必须读,否则 8042 不会发下一次中断),写环形缓冲,发 EOI。 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 MHz 经 12 分频。)
输出频率:
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
配套步骤(下一章):
- IDT 注册 向量 32 (
IRQ0_VECTOR equ 32) init_pit_ch0设置分频- PIC IMR 清 bit0,开放 IRQ0:
IN 0x21→AND AL, 0xFE→OUT 0x21, AL - 定时器 ISR 内
tick++,发 EOIOUT 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
初始化顺序: CLI → init_pic(仅屏蔽 IRQ0)→ init_idt(向量 9 与 33)→ init_8042 → STI。
六、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=1,read_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_char → kbd_pop → HLT 里等键盘中断,不是 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 PIT 以 1.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 + 页表)。
源码: