2. 中断机制概述:硬件中断与软件中断、中断控制器(如APIC/GIC)、中断描述符表(IDT)与中断向量

中断机制是现代操作系统响应异步事件的基石。在Linux多核系统中,中断处理路径的效率直接影响网络吞吐、存储IO和实时响应。本节从硬件中断与软件中断的区分出发,逐层拆解中断控制器、中断描述符表与中断向量的核心概念,为后续中断负载均衡与CPU亲和性配置建立完整的知识框架。

2.1 硬件中断与软件中断

中断按触发来源可分为硬件中断和软件中断两大类,二者在触发方式、处理流程和应用场景上存在本质差异。

特性 硬件中断 软件中断
触发源 外部设备(网卡、磁盘、定时器) CPU执行指令(int、syscall、异常)
异步性 完全异步,与当前执行流无关 同步,由当前指令主动触发
可屏蔽性 可屏蔽(IF标志位控制) 不可屏蔽(异常必须处理)
典型用途 设备IO完成、时钟滴答、电源事件 系统调用、缺页异常、断点调试
中断向量分配 由中断控制器分配(通常32~255) 固定向量(0~31为异常和陷阱)
关键区分:硬件中断是“外来的通知”,软件中断是“内生的请求”。在Linux内核中,硬件中断处理函数(ISR)运行在中断上下文,不能睡眠;软件中断(如softirq、tasklet)运行在软中断上下文,允许一定程度的延迟处理。
性能提示:硬件中断的频繁触发会导致CPU频繁进出中断上下文,增加上下文切换开销。多核环境下,将同一设备的中断绑定到特定CPU核(中断亲和性)可以有效减少缓存抖动和锁竞争。

2.2 中断控制器:APIC与GIC

中断控制器是CPU与中断源之间的桥梁,负责中断的收集、优先级仲裁、路由和投递。x86架构使用APIC(Advanced Programmable Interrupt Controller),ARM架构使用GIC(Generic Interrupt Controller)。二者虽然指令集不同,但核心功能高度相似。

2.2.1 x86 APIC架构

现代x86系统采用本地APIC(Local APIC) + IO APIC的两级架构。每个CPU核集成一个本地APIC,负责接收来自IO APIC的中断消息和核间中断(IPI)。IO APIC位于芯片组中,收集外部设备的中断请求并路由到指定CPU的本地APIC。

x86 APIC 中断路由架构 外部设备 网卡 / 磁盘 / USB 定时器 / 键盘 IO APIC 中断收集 / 路由 优先级仲裁 本地 APIC(每个CPU核) 接收中断消息 核间中断(IPI) 定时器 / 性能监控 CPU Core 0 CPU Core 1 CPU Core N 中断消息格式 向量号 + 目标CPU 触发模式(边沿/电平)
亲和性配置入口:在x86系统中,通过 /proc/irq/{IRQ_NUM}/smp_affinity 文件写入CPU位图,即可将指定中断路由到特定CPU核。底层操作的是IO APIC的红色重定向表项(Redirection Table Entry)。

2.2.2 ARM GIC架构

ARM GIC(Generic Interrupt Controller)是ARM体系的标准中断控制器,目前主流版本为GICv3/GICv4。GIC架构分为分发器(Distributor)和CPU接口(CPU Interface)两部分,支持SPI(共享外设中断)、PPI(私有外设中断)和LPI(本地特定中断)三种中断类型。

中断类型 缩写 目标CPU 典型用途
共享外设中断 SPI 可路由到任意CPU 网卡、USB控制器、MMC
私有外设中断 PPI 固定绑定到特定CPU 本地定时器、性能计数器
本地特定中断 LPI 基于消息的中断 PCIe MSI/MSI-X
ARM GICv3 中断路由架构 外设中断源 SPI / PPI / LPI PCIe MSI-X GIC Distributor 中断优先级 / 使能 路由表 / 亲和性 CPU Interface(每个CPU核) 中断确认 / 结束 优先级掩码 / 抢占 CPU Core 0 CPU Core 1 CPU Core N GIC亲和性配置 通过 Distributor 路由表 设置中断目标CPU掩码

2.3 中断描述符表(IDT)与中断向量

中断描述符表(IDT)是CPU用于查找中断处理函数入口的数据结构。每个中断向量对应IDT中的一个表项,表项中包含处理函数的段选择子和偏移地址。x86架构下,IDT最多支持256个中断向量(0~255),其中前32个(0~31)保留给CPU异常和陷阱。

2.3.1 中断向量分配规则

向量范围 类型 典型用途 可配置性
0 ~ 31 异常 / 陷阱 除零错误(#DE)、缺页(#PF)、通用保护(#GP) 固定,由CPU架构定义
32 ~ 255 硬件中断 / 系统调用 IRQ 0(定时器)、IRQ 1(键盘)、系统调用(int 0x80) 可动态分配和重映射

2.3.2 IDT表项结构(x86-64)

每个IDT表项占用16字节,包含以下关键字段:

// x86-64 IDT 表项结构(gate_desc)
struct gate_struct {
    u16 offset_low;      // 处理函数偏移低16位
    u16 segment;         // 段选择子(指向代码段)
    u16 ist:3,           // 中断栈表索引
        zero:5,
        type:5,          // 门类型(中断门/陷阱门/任务门)
        dpl:2,           // 描述符特权级
        p:1;             // 段存在位
    u16 offset_mid;      // 处理函数偏移中16位
    u32 offset_high;     // 处理函数偏移高32位
    u32 reserved;        // 保留
} __attribute__((packed));
性能关键点:中断门(type=0xE)进入时会自动清除IF标志(禁止嵌套中断),陷阱门(type=0xF)不会修改IF标志。驱动开发中,如果中断处理函数需要支持嵌套抢占,应使用陷阱门或手动操作IF标志。

2.3.3 Linux内核的中断向量管理

Linux内核在启动时初始化IDT,并将硬件中断向量映射到irq_desc数组。每个irq_desc结构体管理一个中断号的中断处理函数链、亲和性设置和统计信息。

Linux 中断向量到 irq_desc 映射关系 IDT(中断描述符表) 向量 0: #DE 除零 向量 1: #DB 调试 ... 向量 32: IRQ 0 定时器 向量 33: IRQ 1 键盘 ... 向量 255: 系统保留 irq_desc[] 数组 irq_desc[0]: 定时器 handler: timer_interrupt affinity: CPU 0-3 irq_desc[1]: 键盘 handler: keyboard_interrupt affinity: CPU 0 ... 中断处理函数 do_IRQ() handle_level_irq() handle_edge_irq() handle_fasteoi_irq() 设备驱动 ISR

2.4 中断处理流程全景

从设备触发中断到驱动ISR执行完毕,完整的中断处理路径涉及硬件、中断控制器、内核中断子系统和设备驱动四个层次。以下流程图展示了x86架构下的典型中断处理路径:

x86 中断处理完整流程 ① 设备触发中断请求 ② IO APIC 接收并路由 ③ 本地APIC 接收中断消息 ④ CPU 查询 IDT 获取入口 ⑤ 执行 do_IRQ() → 驱动 ISR 关键性能指标 中断延迟 = 硬件延迟 + 中断控制器路由 + IDT查找 + ISR执行 多核亲和性可减少延迟
常见误区:很多开发者认为中断亲和性只影响中断处理函数本身。实际上,中断处理函数中触发的软中断(softirq)和tasklet默认在触发中断的CPU上继续执行。如果中断亲和性设置不当,可能导致某个CPU核既处理大量硬中断又处理对应的软中断,造成单核过载而其他核空闲。

2.5 本章小结

  • 硬件中断由外部设备异步触发,通过中断控制器路由到指定CPU;软件中断由CPU指令同步触发,用于系统调用和异常处理。
  • APIC(x86)和GIC(ARM)是两大主流中断控制器架构,均支持中断优先级、路由和亲和性配置。
  • IDT是CPU查找中断处理函数的入口表,每个中断向量对应一个处理函数地址;Linux内核通过irq_desc数组管理中断号和亲和性信息。
  • 中断处理路径涉及设备→中断控制器→CPU→IDT→内核→驱动ISR,每个环节都可能成为性能瓶颈。