Files
Operating-System/IO_Interrupt/Interrupt.md
2026-03-22 17:40:44 +08:00

14 KiB
Raw Permalink Blame History

你可以把中断控制器理解成: 👉 CPU 和所有外部设备之间的“中断调度中心”,专门负责把“谁在打断 CPU”这件事变得可控、有序。

我不绕概念,直接从“它解决什么问题 → 怎么工作 → 现代结构”讲清楚。


一、为什么必须有中断控制器(没有会怎样)

如果没有中断控制器,系统会变成:

键盘 ——┐
网卡 ——┼——→ CPU直接连
磁盘 ——┘

问题:

1CPU根本不知道是谁发的中断
2多个设备同时中断 → 冲突
3优先级无法控制
4无法分配给不同CPU核心

👉 所以必须有一个“中间层”:

设备 → 中断控制器 → CPU

二、中断控制器到底做什么(核心三件事)


1接收中断请求汇聚

多个设备 → 中断控制器

它像一个“汇线器”:

  • 键盘发一个中断
  • 网卡发一个中断
  • 磁盘发一个中断

👉 都先到它这里


2仲裁谁先处理

如果同时来多个:

网卡 + 键盘 同时中断

中断控制器决定:

谁优先(优先级)
谁延后(排队)

3分发发给哪个 CPU

现代是多核:

CPU0 / CPU1 / CPU2 / CPU3

中断控制器可以:

把网卡中断发给 CPU2
把键盘中断发给 CPU0

👉 这就是“中断亲和性IRQ affinity


三、现代中断控制器APIC

这是 x86 现在的主流结构。


它分成两部分(非常关键)


1IOAPIC外部中断入口

设备 → IOAPIC

作用:

收集来自设备的中断IRQ / MSI

2Local APIC每个 CPU 一个)

每个 CPU core 内部都有一个

作用:

接收中断
决定是否打断当前CPU执行

整体结构

设备 → IOAPIC → Local APIC某个CPU → CPU核心

四、现代中断是怎么“传”的(重点)


方式 1传统 IRQ

设备 → 电线IRQ → IOAPIC

方式 2MSI现代主流

设备做的是:

发一次 PCIe 写操作

写到 APIC 的某个寄存器:

设备 → PCIe → Local APIC

👉 这次写 = “中断信号”


五、CPU 眼里中断控制器是什么

从 CPU 角度:

Local APIC
    有一个“中断队列”

当有中断:

APIC 告诉 CPU
    “有中断 vector = 33”

CPU才会

查 IDT → 跳 handler

👉 所以:

中断控制器 ≠ 执行中断
中断控制器 = 告诉CPU“该执行哪个中断”

六、一个完整例子(键盘)

1键盘产生 scan code

2控制器发中断

3IOAPIC 接收

4IOAPIC
    - 查配置
    - 决定 vector = 33
    - 选择 CPU0

5Local APICCPU0收到

6CPU0
    - 当前指令结束
    - 进入中断处理

7查 IDT[33] → keyboard handler

七、再给你一个“工程级理解”

你可以把中断控制器当成:

“中断调度器”

类似 CPU 调度器:

CPU调度器 中断控制器
管线程 管中断
决定谁运行 决定谁打断CPU
分配CPU 分配给哪个CPU

八、你现在应该形成的模型

设备:
    产生事件

DMA
    搬数据

中断控制器:
    调度中断

CPU
    执行中断处理

九、最后一句“压缩理解”

中断控制器 = 把“混乱的硬件中断”变成“有序、可调度的CPU事件”

如果你继续往下走,我建议下一步:

👉 “中断上半部 / 下半部softirq / tasklet是怎么拆分工作的”

这一步会让你理解:

  • 为什么中断不能干太多事
  • Linux 是怎么把中断变成“异步任务系统”的

不是一个东西,而且在现代系统里它们是明确分层、分工协作的两类硬件。你可以把它们想成两级:

设备(键盘/网卡/磁盘)
   ↓
设备控制器device controller / host controller发中断请求IRQ 或 MSI
中断控制器interrupt controller, APIC
   ↓
CPU

下面把三件事讲清:谁是谁、信号怎么走、怎么“分配/配置”


一、设备控制器 vs 中断控制器:本质区别

1设备控制器Device Controller

  • 位置:贴近设备(有时在设备里,有时在主板/芯片组里)

  • 作用:把具体设备“变成可编程接口”

    • 提供寄存器MMIO给 CPU 配置
    • 管 DMA把数据搬到内存
    • 维护队列/缓冲ring、FIFO 等)
    • 在需要时“发出中断请求”

例子:

  • USB 键盘 → USB Host ControllerxHCI
  • 网卡 → NIC 控制器(在网卡芯片里)
  • 磁盘 → SATA/AHCI 或 NVMe 控制器

👉 它关心的是:设备数据 & 何时通知 CPU


2中断控制器Interrupt Controller

  • 位置CPU/主板一侧(每核一个本地控制器 + 全局入口)

  • 作用:把“谁在请求中断”这件事变成可调度事件

    • 接收中断请求(来自很多设备/控制器)
    • 仲裁优先级
    • 选择目标 CPU 核心
    • 告诉 CPU用哪个“中断向量vector

现代 x86 是 APIC 体系:

  • IOAPIC外部入口接收 IRQ或配合转发
  • Local APIC每个 CPU 核一个(最终递送给该核)

👉 它关心的是:哪个中断先、送到哪个核、对应哪个 vector


一句话区分

设备控制器:产生事件 +(可选)发中断
中断控制器:调度这些中断给 CPU

二、信号是怎么从设备“走到 CPU”的

分两种路径:老式 IRQ 线现代 MSI主流


路径 A传统 IRQ电信号

设备控制器 ──IRQ线──> IOAPIC ──> Local APIC某核 ──> CPU

流程要点:

  1. 设备控制器把某根 IRQ 线拉高
  2. IOAPIC 收到,查自己的“重定向表”(决定 vector/目标核)
  3. 转发给某个 CPU 的 Local APIC
  4. CPU 在指令边界响应,中断进入内核

路径 BMSI / MSI-X现代主流

没有“中断线”,而是一次“特殊的内存写”

设备控制器 ──(PCIe 写)──> Local APIC目标核 ──> CPU

要点:

  1. 驱动在初始化时告诉设备:“中断请写到这个地址/数据”APIC 的 MSI 门铃)
  2. 设备控制器在合适时机发起一笔 PCIe 写doorbell
  3. 这笔写被 APIC 解释为“中断到达(带某个 vector
  4. CPU 响应并跳转到对应 handler

👉 这也是为什么 MSI 能:

  • 不用物理中断线(可扩展)
  • 直接指定目标 CPU(天然支持多核分发)

三、“怎么分配的”:谁决定 vector、优先级、目标 CPU

这里有三层配置,主要由操作系统驱动在启动/设备初始化时完成。


1中断向量vector分配

  • OS 维护 IDT中断向量表0255
  • 给每个设备/队列分配一个或多个 vector
  • 驱动注册 handlerISR
vector → IDT[vector] → handler地址

2路由/目标 CPUaffinity

  • 在 APIC/IOAPIC 的配置里(或 MSI 的消息里)

  • 指定:

    • 这个中断送到哪个 CPU或一组 CPU

常见策略:

  • 键盘 → CPU0低频、简单
  • 网卡多队列 → 分散到 CPU0..CPU7并行处理

3优先级与屏蔽masking

  • 中断控制器有优先级/屏蔽位

  • OS 可以:

    • 临时屏蔽某个中断
    • 调整优先级(谁可以打断谁)

四、把三者串起来(一次真实事件)

以“网卡收包(带 DMA + MSI”为例

1驱动初始化
    - 分配 RX ring内存
    - 配置 DMA给设备 IOVA/PA
    - 配置 MSIvector=80目标=CPU2

2网卡收到数据包
3网卡控制器 DMA → 把包写入内存 buffer
4根据策略可能合并多个包
    网卡控制器 发 MSIPCIe 写到 APIC

5Local APICCPU2接收
    产生中断 vector=80

6CPU2 在指令边界:
    - 保存现场
    - 查 IDT[80]
    - 跳到网卡 ISR

7驱动
    - 从 ring buffer 取一批包NAPI 轮询)
    - 交给协议栈

五、常见误区一把清

1“中断控制器和设备控制器是一个东西吗?” → 不是。前者是调度/路由中断,后者是管理设备并发起中断

2“中断控制器接收设备控制器的信号吗?” → 是的。

  • 传统:接收 IRQ 线
  • 现代:接收 MSI经 PCIe 的写事务,被 APIC 解释为中断)

3“PCIe 在这中间干嘛?” → 只是传输通道/协议:承载 DMA 数据和 MSI“门铃写”。


六、压缩成三句记忆

设备控制器:管理设备 + 何时发中断
中断控制器把中断变成“发给哪个CPU的哪个vector”
CPU按 vector 查 IDT执行 handler

如果你想再往下一层,我可以把 “一个中断里为什么要拆成上半部/下半部hardirq/softirq/NAPI 的内核执行模型连同关键函数路径一起走一遍。


你现在这个直觉方向是对的,但混了两层东西: 👉 “谁决定 CPU 运行什么” vs “谁能打断 CPU”

我帮你把它压成一个非常干净的统一模型,把“线程”和“中断”放在同一张图里,但区分清楚它们的角色。


一、先给你一句最本质的话

线程CPU“主动选择”运行的任务调度器决定
中断CPU“被动被打断”的事件硬件触发

👉 这两者是正交的两套机制,但会交织在一起。


二、统一模型(你要在脑子里建立这个)

CPU执行的东西其实只有三种

1普通执行流线程
2中断处理interrupt
3异常/系统调用exception/syscall

你可以画成:

        ┌───────────────┐
        │   线程 A       │
        └──────┬────────┘
               │
        (被打断)
               ↓
        ┌───────────────┐
        │   中断处理     │
        └──────┬────────┘
               │
        (返回)
               ↓
        ┌───────────────┐
        │   线程 A / B   │
        └───────────────┘

三、线程 vs 中断:谁在“控制 CPU”


1线程你写的程序

来源:

进程 / 线程(用户程序 or 内核线程)

特点:

✔ 由操作系统调度scheduler
✔ 通过时间片切换
✔ 是“长期执行的任务”

比如:

你在跑:
- 浏览器
- shell
- 你的C++程序

2中断硬件触发

来源:

网卡 / 键盘 / 磁盘 / 定时器

特点:

✔ 异步发生
✔ 可以随时打断线程
✔ 执行时间很短(必须短)

四、你那句话的“正确版本”

你说:

中断让CPU调整运行什么内容

✔ 可以这么理解,但要改成:

中断 ≠ 决定运行什么
中断 = 打断当前执行让CPU先处理紧急事件

真正“决定运行什么”的是:

调度器scheduler

五、一个关键细节(很多人卡这里)

👉 中断本身不会直接切换线程


实际流程是这样的:

线程A 正在运行
   ↓
(网卡中断)
   ↓
CPU 进入中断处理
   ↓
中断处理函数执行
   ↓
可能触发调度schedule
   ↓
切换到线程B

👉 重点:

中断只是“触发点”
线程切换是“调度器决定”

六、三者关系(最重要的一张图)

        硬件(网卡/键盘)
               ↓
            中断
               ↓
        中断处理函数
               ↓
        (可能唤醒线程)
               ↓
        调度器scheduler
               ↓
        选择线程运行

七、一个真实例子(你更容易理解)

你写了一个:

read(socket, buf, n);

实际发生:

1线程A你的程序调用 read
2没有数据 → 线程A 被阻塞sleep

3网卡收到数据
4DMA 写入内存
5网卡发中断

6CPU处理中断
    → 把数据放入socket buffer
    → 标记线程A可以运行了

7调度器
    → 把线程A加入就绪队列

8某个时间点
    → CPU 切回线程A

👉 核心逻辑:

中断:负责“数据到了”
调度器:负责“谁来处理数据”
线程:真正处理数据

八、再帮你把概念压缩成三层


1执行层CPU在干嘛

线程 / 中断 / 异常

2控制层谁决定切换

中断:打断
调度器:选择

3数据层为什么切换

IO完成 / 定时器 / 系统调用

九、一个非常重要的纠正

你现在的模型是:

中断 = CPU调度方式

不准确

正确模型:

中断 = CPU的“异步事件入口”
调度 = CPU的“任务选择机制”

十、给你一个最简终极理解(建议背)

CPU一直在跑“线程”
中断只是偶尔插进来的一段“紧急代码”
调度器才决定接下来跑哪个线程

十一、你现在已经到了一个关键分界点

接下来你如果再深入一层,会进入:

  • 为什么中断不能做太多事(→ 下半部)
  • Linux 的 softirq / tasklet / workqueue
  • 抢占preempt vs 中断

如果你愿意,我可以下一步帮你做一个**“一条时间线(纳秒级)”**

👉 从“网卡收包 → 中断 → softirq → 唤醒线程 → 用户 read 返回”

这个会把你现在所有概念彻底锁死,不再混。