Intel 8259A 可编程中断控制器(PIC)—— 工作原理、编程接口与演进
现代 x86 中断投递由两级 APIC(Local APIC + I/O APIC)完成(见 irq-affinity §二),但理解 8259A 可编程中断控制器(Programmable Interrupt Controller, PIC) 是理解中断控制器进化史的第一块基石。它不仅定义了"IRQ 编号"、"优先级链"、"中断向量"、"EOI 应答"等核心概念,而且 Linux 至今沿用 8259 时代的 IRQ 0-15 编号体系、IO-APIC 保留 ExtINT 兼容模式、多核启动初期仍靠 8259 投递中断——8259A 从未真正消失。本篇参照 dma-8237 的"祖宗芯片"母题风格,细致拆解 8259A 的硬件架构、全部初始化/操作寄存器逐位分解、主从片级联、完整中断响应时序(两个 INTA 周期)、以及到 APIC 的进化路线。
零、8259A 在历史上的位置
8259A 是 Intel 在 1980 年代推出(8086/8088 时代配套)的可编程中断控制器。每片 8259A 管理 8 条 IRQ 线,PC/AT 用 两片级联(主片 IRQ2 → 从片 INT) 提供 15 个有效中断输入(IRQ0-7 + IRQ8-15)。
IBM PC/AT 8259A 级联架构:
主 8259A (端口 0x20/0x21, 中断向量基址 0x08):
IRQ0 ─── 系统定时器 (PIT 8254, 18.2Hz tick, 现代 APIC Timer 的前身)
IRQ1 ─── 键盘控制器 (8042)
IRQ2 ─── 级联到从 8259A ← 这是关键!主片 IRQ2 不接设备,接的是从片的 INT 输出
IRQ3 ─── 串口 COM2/COM4
IRQ4 ─── 串口 COM1/COM3
IRQ5 ─── 声卡 / 并口 LPT2
IRQ6 ─── 软盘控制器
IRQ7 ─── 并口 LPT1
主 8259A INT 引脚 → CPU INTR 引脚
↑
级联 (INTA 同步)
↓
从 8259A (端口 0xA0/0xA1, 中断向量基址 0x70):
IRQ8 ─── 实时时钟 (RTC)
IRQ9 ─── (空闲 / ACPI)
IRQ10 ─── (空闲)
IRQ11 ─── (空闲)
IRQ12 ─── PS/2 鼠标
IRQ13 ─── 数学协处理器 (x87 FPU)
IRQ14 ─── 主 IDE 控制器
IRQ15 ─── 从 IDE 控制器关键限制:8259A 是 8 位 ISA 时代的产物——只有 8 条 IRQ 输入/片、主从级联才凑出 15 条、固定优先级链(IRQ0 > IRQ1 > ... > IRQ7)、不支持多核路由(所有中断只能投给 BSP)、INTA 周期慢(两个总线周期才能拿到向量号)。以上全部是 APIC 要解决的问题。但 8259A 定义的"向量号+EOI 应答+优先级仲裁"三要素至今未变。
一、硬件架构
1.1 内部框图

1.2 关键引脚
| 引脚 | 方向 | 功能 |
|---|---|---|
| IR0~IR7 | 输入 | 8 条中断请求线,外设以此请求中断 |
| INT | 输出 | 连接到 CPU 的 INTR 引脚——8259 告诉 CPU:"有中断" |
| INTA# | 输入 | CPU 发出的中断应答(两个脉冲周期)——8259 在此周期内把向量号放到数据总线上 |
| D7~D0 | 双向 | 数据总线——CPU 用它读写寄存器,8259 用它发送向量号 |
| CS# | 输入 | 片选——为低时 CPU 才能访问这块 8259 |
| RD# / WR# | 输入 | 读/写选通 |
| A0 | 输入 | 地址线——区分偶端口(ICW1/OCW2/OCW3)和奇端口(ICW2/3/4/OCW1/IMR) |
| CAS0~CAS2 | 双向 | 级联线——主片输出(告诉从片"轮到你应答"),从片输入(与自身 ID 比较) |
| SP#/EN# | 输入/输出 | 主/从选择——硬件拉高=主片,拉低=从片 |
1.3 三大核心寄存器
这是理解 8259A 的最关键一张图——三个 8-bit 寄存器之间的协作关系:

三寄存器在不同阶段的值(以 IRQ1 键盘中断为例):
| 时刻 | IRR (bit1) | ISR (bit1) | IMR (bit1) | 发生了什么 |
|---|---|---|---|---|
| 空闲 | 0 | 0 | 0 | — |
| 键盘按下 | 1 | 0 | 0 | 8259 锁存 IRQ1 到 IRR |
| CPU 正在处理更高优先级 | 1 | 0 | 0 | 仲裁器等待 |
| CPU 发 INTA | 1→0 | 0→1 | 0 | IRR 对应位清除,ISR 对应位置位 |
| 中断 handler 写 EOI | 0 | 1→0 | 0 | ISR 对应位清除,该优先级可以再来 |
二、编程接口 —— 全部寄存器逐位拆解
2.0 端口映射与命令分类
8259A 的寄存器通过 两组端口 + 读写方向 + 命令字 D4 位 复用:
| 端口 (主片) | 端口 (从片) | A0 | 读 | 写 |
|---|---|---|---|---|
| 0x20 | 0xA0 | 0 | IRR 或 ISR(由 OCW3 选择) | ICW1(D4=1)/ OCW2(D4=0, D3=0)/ OCW3(D4=0, D3=1) |
| 0x21 | 0xA1 | 1 | IMR(读 OCW1) | ICW2/3/4(初始化序列)/ OCW1(IMR) |
区分 ICW 还是 OCW:初始化阶段必须在复位后严格按顺序写入 ICW1→ICW2→ICW3→ICW4,写入同一端口(A0=0);之后所有写入同一端口的都是 OCW,通过 D4 和 D3 区分。
2.1 初始化命令字 ICW1 —— 触发初始化、配置触发方式
| 位 | 名称 | 含义 |
|---|---|---|
| D7-D5 | — | 8086/88 模式下必须为 0(仅 80x 模式下有意义:中断向量基址 A7-A5) |
| D4 | 必须为 1 | 这是"我是 ICW1 不是 OCW2/3"的标志位 |
| D3 | LTIM | Level Triggered Mode:1=电平触发,0=边沿触发 |
| D2 | ADI | 地址间隔:8086 默认=1(4 字节间隔),80x=0(8 字节间隔) |
| D1 | SNGL | Single:1=只有一片 8259,0=多片级联(需要 ICW3) |
| D0 | IC4 | ICW4 Needed:1=需要写 ICW4,0=不需要(8080/85 模式) |
2.2 初始化命令字 ICW2 —— 中断向量基址
| 位 | 含义 |
|---|---|
| D7-D3 | 中断向量号的高 5 位(低 3 位由 IRQ 编号自动填充) |
| D2-D0 | 8086 模式下必须为 0(IRQ0→向量基址+0,IRQ1→基址+1,...) |
Linux 经典设置:ICW2=0x20(主片,向量 0x20-0x27),ICW2=0x28(从片,向量 0x28-0x2F)。注意这与 IA-32 保护模式下 CPU 保留向量 0-31 冲突——这正是 APIC 解决了的问题之一。
2.3 初始化命令字 ICW3 —— 级联配置
主片的 ICW3(位图,标记哪个 IR 接了从片):
| 位 | 含义 |
|---|---|
| D7-D0 | bit=1 表示对应 IR 输入接了从片。PC/AT:D2=1(IRQ2 级联从片),其余=0 |
从片的 ICW3(ID 号,告诉从片"你在主片的哪条 IR 线上"):
| 位 | 含义 |
|---|---|
| D7-D3 | 必须为 0 |
| D2-D0 | 从片 ID(PC/AT:=2,即 IRQ2) |
2.4 初始化命令字 ICW4 —— 工作模式
| 位 | 名称 | 含义 |
|---|---|---|
| D7-D5 | — | 必须为 0 |
| D4 | SFNM | Special Fully Nested Mode:1=特殊全嵌套(主片向从片开放同优先级的多个 IRQ),0=普通全嵌套 |
| D3-D2 | BUF | 缓冲模式:0x=非缓冲,10=缓冲/从片,11=缓冲/主片 |
| D1 | AEOI | Auto EOI:1=自动(INTA 第二个周期自动清 ISR),0=手动(中断 handler 结束前须写 EOI 命令) |
| D0 | µPM | Microprocessor Mode:1=8086/88 模式,0=8080/85 模式 |
2.5 操作命令字 OCW1 —— 设置 IMR(中断屏蔽寄存器)
写入奇端口(0x21/0xA1),最常用的一个寄存器:
OCW1 (IMR):
D7 D6 D5 D4 D3 D2 D1 D0
│ │ │ │ │ │ │ │
IR7 IR6 IR5 IR4 IR3 IR2 IR1 IR0
1=屏蔽(禁止该 IRQ),0=允许cli/sti 控制的是 CPU 端(IF 标志,禁止/允许一切可屏蔽中断),IMR 控制的是 8259 端(选择性屏蔽特定 IRQ)。
2.6 操作命令字 OCW2 —— 优先级循环 + EOI 命令
写入偶端口(0x20/0xA0):
| 位 | 含义 |
|---|---|
| D7 | R(Rotation):1=开启优先级循环 |
| D6 | SL(Specific Level):1=配合 L2-L0 指定特定中断级别 |
| D5 | EOI(End of Interrupt):1=发送 EOI 命令 |
| D4 | 必须为 0(与 ICW1 的 D4=1 区分)+ D3 必须为 0(与 OCW3 区分) |
| D2-D0 | L2-L0:当 SL=1 时指定 IRQ 级别 |
最常用的组合:
| R | SL | EOI | 组合 | 俗称 | 含义 |
|---|---|---|---|---|---|
| 0 | 0 | 1 | 0x20 | Non-Specific EOI | 对当前 ISR 中最高优先级位清零——最常用 |
| 0 | 1 | 1 | 0x60+IRn | Specific EOI | 指定清零 ISR 中某一位 |
2.7 操作命令字 OCW3 —— 读寄存器选择 + 特殊屏蔽/轮询模式
| 位 | 含义 |
|---|---|
| D7 | 必须为 0(ESMM 之前保留) |
| D6 | ESMM(Enable Special Mask Mode):1=允许 D5 控制特殊屏蔽模式 |
| D5 | SMM(Special Mask Mode):1=特殊屏蔽模式(允许低优先级中断打断高优先级 ISR) |
| D4 | 必须为 0 |
| D3 | 必须为 1(这是"我是 OCW3"的标志) |
| D2 | —(0=无操作, 1=轮询命令) |
| D1 | RR(Read Register):1=准备读 IRR/ISR |
| D0 | RIS(Read ISR):读 ISR=1, 读 IRR=0 |
三、中断优先级的仲裁机制
3.1 固定优先级(Fully Nested Mode,默认)
IRQ0 最高 → IRQ7 最低。这是 8259A 上电默认行为和 Linux 最常用模式:
优先级:IRQ0 > IRQ1 > IRQ2 > IRQ3 > IRQ4 > IRQ5 > IRQ6 > IRQ7
当前 CPU 正在处理 IRQ4 时:
IRQ3 到达 → 可以打断(IRQ3 优先级 > IRQ4)← 中断嵌套
IRQ5 到达 → 等待(IRQ5 优先级 < IRQ4)
IRQ4 到达 → 等待(同优先级不嵌套)中断嵌套的完整时序:

3.2 循环优先级(Rotating Priority)
- 自动循环:每次 EOI 后,刚被服务的 IRQ 变成最低优先级,其下一个 IRQ 升为最高
- 指定循环:写 OCW2(R=1, SL=1, L2-L0=指定)将指定某级为最低
Linux 不使用旋转优先级——它由内核的 softirq+workqueue 机制解决公平性问题。但理解它是理解"为什么 XT-PIC 和 APIC 都选择固定优先级"的前提。
3.3 特殊屏蔽模式(Special Mask Mode)
常规模式下,只要 ISR 有任何位置位,同优先级或更低优先级的中断都被阻塞。写入 OCW3(ESMM=1, SMM=1)后,只比较 当前 IMR,不考虑 ISR:
用途:DMA 中断 handler 想允许低优先级定时器中断打断自己,又不希望通过 EOI 释放 ISR 锁四、完整中断响应时序 —— 两个 INTA 周期详解
这是 8259A 最核心的硬件行为。从设备拉高 IRQ 到 CPU 拿到向量号,8259A 与 CPU 之间有严格的握手协议:

关键设计点:
- INTA 分两个周期:第一个周期冻结仲裁+记录在案,第二个周期才出数据——这保证向量号对应的就是刚才仲裁出来的那个 IRQ
- AEOI 的陷阱:自动 EOI 让 handler 不必写 EOI,但也意味着 handler 还没执行完,同优先级中断就可以再次进入——可能导致中断重入(reentrancy)。Linux(在 PIC 模式下)使用手动 EOI,handler 末尾才写。
五、主从级联机制
5.1 电气连接
CPU INTR ←── 主片 INT
↑
主片 IRQ2 ←── 从片 INT
↑
从片 IRQ8~15 ←── 外设
INTA"广播":CPU 的 INTA# 脉冲同时到达主片和从片
CAS 路由:主片在第一个 INTA 周期驱动 CAS0~2,告知从片该谁回答5.2 级联下的中断响应序列
设备 IRQ10 请求中断:
① 从片 IRR bit2=1
② 从片仲裁通过 → 从片 INT 拉高
③ 主片 IRQ2 引脚输入 → 主片 IRR bit2=1
④ 主片仲裁通过 → 主片 INT 拉高
⑤ CPU 发 INTA 第一个脉冲:
主片:IRR bit2 清, ISR bit2 置, 发现是级联 IRQ → CAS0~2=010(从片ID=2)
从片:收到 CAS=010, 匹配 ID → IRR bit2 清, ISR bit2 置
⑥ CPU 发 INTA 第二个脉冲:
从片:因为 CAS 匹配,从片驱动 D7~D0 输出向量号(0x72)
主片:因为检测到是级联, 主片不驱动总线
⑦ CPU 拿到向量号 0x72 → 执行 IRQ10 handler
⑧ handler 末尾写 EOI:
先写从片(0xA0): 从片 ISR bit2 清 → 从片 INT 拉低 → 主片 IRQ2 恢复电平
再写主片(0x20): 主片 ISR bit2 清为什么必须先写从片 EOI 再写主片? 因为从片的 ISR bit2 置位导致从片 INT 线持续保持高电平,从而主片 IRQ2 依然是"请求中"状态。只有先清从片 ISR,从片 INT 才能拉低,主片 IRQ2 才能真正"完成"——此时主片 EOI 才有意义。反过来写(先主片、后从片)会导致主片 ISR 清掉后立即收到一个假的 IRQ2 请求。
5.3 特殊全嵌套模式(Special Fully Nested Mode, SFNM)
默认全嵌套模式下,主片 ISR bit2 置位后,从片所有 IRQ8~15 都被主片挡住(主片只看到 IRQ2,不知从片内部还有优先级差别)。SFNM(ICW4 D4=1)解决此问题:
默认:从片 IRQ10 正在服务 → 主片 ISR bit2=1 → IRQ9 想进来
→ 主片仲裁:IRQ9 在主片看就是 IRQ2,优先级=IRQ2 → ISR 已有 IRQ2 → 不通过
❌ IRQ9 被错误地阻塞
SFNM:主片 ISR bit2=1 时,主片不比较 IRQ2 的优先级——只要从片自己仲裁通过(IRQ9>IRQ10),
主片就不挡,INT 照样发给 CPU
✔ IRQ9 可以打断 IRQ10六、Linux 如何对待 8259A
6.1 启动阶段:PIC 模式
现代 x86 Linux 启动时的中断控制器切换过程:
BIOS / 固件
│ 默认 8259A PIC 模式(BIOS 已初始化 ICW1-4)
│ 所有中断(键盘/定时器/磁盘)由 8259A 投递
▼
Bootloader (GRUB/syslinux)
│ 仍然是 PIC 模式
▼
Linux 内核早期启动 (arch/x86/boot/)
│ 还是 PIC 模式——kernel 的前几条指令就是被 PIC 的 timer IRQ0 打断的
▼
setup_local_APIC() + apic_intr_mode_init()
│ 探测 ACPI MADT 表 → 发现 IO-APIC → 决定切换到 APIC 模式
│ 也有可能因 BIOS bug / 硬件不支持 → 退回到 PIC 模式 (XTPIC)
▼
最终运行模式:
├── 通常 → 对称 I/O 模式 (Symmetric I/O Mode):所有中断走 IO-APIC
├── 有时 → 8259A Virtual Wire 模式:8259A 只转发时钟中断到 Local APIC LINT0
└── 罕见 → XT-PIC 兼容模式:完全回退到 8259A6.2 /proc/interrupts 的 IRQ 编号
即使运行在 APIC 模式下,Linux /proc/interrupts 的 IRQ 编号仍然沿袭 8259A 时代:
CPU0 CPU1
0: 42 0 IO-APIC-edge timer ← 继承 8259 IRQ0
1: 0 0 IO-APIC-edge i8042 ← 继承 8259 IRQ1
8: 1 0 IO-APIC-edge rtc0 ← 继承 8259 IRQ8
12: 4 0 IO-APIC-edge i8042 ← 继承 8259 IRQ12
23: 0 0 IO-APIC-fasteoi ehci_hcd ← IO-APIC 自己的引脚 23
24: 520000 480000 PCI-MSI-edge eth0-TxRx-0 ← MSI 中断,IRQ 号是动态分配的
传统 IRQ 0-15:固定给 8259A 的遗留设备
IO-APIC IRQ(通常 16+):IO-APIC 重定向表的引脚
PCI MSI/MSI-X IRQ(动态分配):设备直接写 Local APIC,不经过 IRQ 线6.3 多核启动时的 8259A
多核系统启动初期,BSP(Boot Strap Processor)先跑,此时:
- 所有中断(包括 AP 的 SIPI)都由 8259A 投递
- BSP 初始化 Local APIC + IO-APIC
- 逐步把 IRQ 从 8259A 迁移到 IO-APIC
- 最终 mask 掉 8259A(通过 IMR=0xFF),所有中断走 IO-APIC
七、8259A(PIC)到 APIC 的进化路线
7.1 为什么需要 APIC?8259A 的六宗罪
| 问题 | 8259A | APIC 解决方案 |
|---|---|---|
| 多核支持 | 所有中断只能投给 BSP——4 核机器只有 1 核处理所有中断(CPU0 %soft 100%) | IO-APIC 重定向表的 Destination Field 可以指定任意 CPU(物理/逻辑模式),Local APIC 每核一个独立接收 |
| 中断线数量 | 最多 15 根(主从级联)——现代服务器有几十上百个 PCIe 设备 | IO-APIC 有 24 个引脚,MSI/MSI-X 不占引脚(用 PCIe 写消息),理论上每个设备可有 2048 个 MSI-X 向量 |
| 中断优先级 | 固定静态链 IRQ0>IRQ1>...>IRQ7——无法动态调整 | Local APIC 的 TPR(Task Priority Register)支持动态优先级阈值;Linux 用向量号分布(高向量→高优先级) |
| 投递延迟 | 两个 INTA 总线周期(ISA 时代约 1μs) | MSI/MSI-X 用 PCIe MWr(Memory Write),一个 TLP 搞定,延迟约 100ns |
| 向量号冲突 | 与 CPU 保留向量 0-31 重叠(ICW2 传统设置 0x20) | IO-APIC 的向量号字段 8-bit(0-255),与 CPU 异常向量不重叠 |
| 亲和性 | 无——所有中断只能到同一 CPU | 支持手动(/proc/irq/N/smp_affinity)、自动负载均衡(irqbalance) |
7.2 进化路线图

7.3 8259A 留下的遗产
即使现代系统完全不用 8259A 了,它的设计遗产无处不在:
| 遗产 | 现代体现 |
|---|---|
| IRQ 编号 0-15 | Linux 内核保留前 16 个 IRQ 号给 legacy 设备,日志和 /proc/interrupts 至今沿用 |
| 中断向量号 = 基址 + 偏移 | IDT 的向量号机制,MSI-X 的 Message Data 也只是换了个地方写向量号 |
| EOI(End of Interrupt) | Local APIC 的 EOI 寄存器(偏移 0xB0),内涵一模一样——告诉中断控制器"这个中断处理完了" |
| IMR(中断屏蔽) | Local APIC 的 LVT 条目有 Mask 位(bit 16),和 IMR 是同一种思想 |
| 优先级仲裁 | Local APIC 的 TPR/PPR 机制本质上就是 8259 优先级链的向量化版本 |
| Virtual Wire 模式 | APIC 的 LINT0 可配置为 ExtINT 模式,模拟 8259 的 INT 行为——用于兼容不支持 APIC 的 OS |
八、观测与实验
8.1 判断当前系统是否还在用 8259A
# 1. 查看 dmesg 中的 PIC→APIC 切换信息
dmesg | grep -i "pic\|8259\|apic\|xtpic"
# 2. 查看当前中断控制器模式
cat /proc/interrupts | head -20
# 如果显示 "XT-PIC" 说明还在用 8259(极为罕见,通常是虚拟机或非常老的机器)
# 如果显示 "IO-APIC" / "PCI-MSI" 说明已迁移到 APIC 模式
# 3. 查看内核 cmdline——是否强制关闭了 APIC
cat /proc/cmdline
# 如果包含 "noapic" 或 "nolapic" → 可能回退到了 PIC 模式
# 如果包含 "pic=noirq" → 强制禁止 8259A 参与中断投递8.2 在 QEMU 中观察纯 8259A 环境
# 创建一台禁用 APIC 的虚拟机
qemu-system-x86_64 \
-cpu host,-apic \ # 禁用 CPU 内 APIC
-machine kernel-irqchip=off \ # 强制使用 8259 用户态模拟
-m 512M \
-kernel /boot/vmlinuz-xxx \
...
# 机器启动后 /proc/interrupts 应显示 XT-PIC 而不是 IO-APIC九、一句话总结
8259A 是所有现代中断控制器的"祖宗"——它定义了"IRQ 编号+向量号+EOI 应答+优先级仲裁"四要素,至今活在 Linux 的 IRQ 命名体系和 Local APIC EOI 寄存器里。每片管 8 条 IRQ、两片级联凑出 15 条、通过 INTA 两个周期握手出向量号、ICW1~4 四步初始化、OCW1~3 三步操作——这些看似简陋的设计,奠定了中断控制器 40 年的编程范式。理解 8259A 的硬件机制(IRR/ISR/IMR 三寄存器协作 + INTA 握手 + 级联 EOI 顺序),才能理解 APIC 为什么要加 Local APIC(多核分发)/ MSI-X(跳过 IO-APIC 直接写)/ x2APIC(32-bit ID 支持超大系统)——以及为什么
/proc/interrupts的第 0 号永远叫 timer。更广泛的 APIC 中断投递机制见 irq-affinity,中断内核处理流程见 interrupts。
配套:中断控制器进化见 irq-affinity §二(APIC 中断投递系统),中断完整分类与流程见 interrupts,APIC 寄存器速查见 x86-64-registers §七,DMA 控制器(同为 PC/AT 时代核心外设芯片)见 dma-8237,系统总线如何连接外设见 pcie。