1. 中断基础:什么是中断、中断向量表、中断处理流程

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即关注的事件。在 Linux 内核中,中断是驱动 I/O 设备、网络包收发、定时器以及异常处理的核心机制。理解中断的基础概念、向量表结构以及上下半部处理流程,是后续配置 irqbalance 和进行 CPU 绑定 的前提。

1.1 什么是中断

中断本质上是一种 事件通知机制。当外部设备(如网卡、磁盘控制器)完成数据传输,或内部定时器到期,设备通过中断控制器(如 APIC、GIC)向 CPU 发送一个电信号。CPU 在每条指令执行完毕后检查中断引脚,若检测到中断信号且中断未被屏蔽,则暂停当前任务,保存上下文,跳转到预定义的中断处理程序执行。

关键特性:
  • 异步性:中断的发生与 CPU 当前执行的指令流无关,属于硬件驱动的异步事件。
  • 优先级:中断控制器为每个中断源分配优先级,高优先级中断可以抢占低优先级中断的处理。
  • 可屏蔽性:CPU 可以通过 EFLAGS 中的 IF 标志位(x86)或 PSTATE 中的 I 位(ARM64)全局屏蔽/开启中断。

在 Linux 内核中,中断被分为两种类型:

类型 触发方式 典型来源 是否可屏蔽
硬件中断 外部设备通过中断控制器引脚触发 网卡(RX/TX)、磁盘(NVMe/SATA)、USB 是(通过 IF 标志或中断掩码)
软件中断 CPU 执行 INT n 指令触发 系统调用(int 0x80)、异常(缺页、除零) 部分可屏蔽(异常不可屏蔽)

1.2 中断向量表

中断向量表(Interrupt Vector Table, IVT)是操作系统或固件在内存中维护的一张表,表中每个条目(向量)对应一个中断号,存储着该中断对应的处理程序入口地址。当 CPU 收到中断信号时,根据中断号从向量表中取出地址,跳转执行。

在 x86 架构中,中断向量表在实模式下位于物理地址 0x0000 处,每个向量占 4 字节(段:偏移)。在保护模式下,由 中断描述符表(IDT) 替代,每个描述符占 8 字节,包含选择子、偏移量、类型和 DPL 等信息。Linux 内核在启动时初始化 IDT,将每个中断向量绑定到对应的内核处理函数。

注意:在 Linux 中,硬件中断对应的中断号通常从 32(0x20)开始,以避免与 CPU 内部异常(0–31)冲突。例如,IRQ 0(定时器)映射到 IDT 向量 32,IRQ 1(键盘)映射到向量 33,以此类推。
中断向量表(IDT)结构示意图 中断描述符表(IDT)结构 向量号 处理程序入口地址 类型/属性 DPL 典型用途 0–31 异常处理函数 陷阱/故障/中止 0(内核态) 缺页、除零、断点 32–47 IRQ 0–15(PIC) 中断门 0 定时器、键盘、串口 48–255 IRQ 16+(MSI/IOAPIC) 中断门 0 网卡、NVMe、GPU 说明: IDT 存储在内存中,由 lidt 指令加载。每个描述符包含 16 位段选择子、32 位偏移量(x86)以及类型属性。 Linux 内核在 arch/x86/kernel/idt.c 中静态定义默认 IDT,并在启动时动态注册设备中断。

1.3 中断处理流程:上半部与下半部

中断处理程序必须快速执行,因为中断期间 CPU 会屏蔽同级或更低优先级的中断,导致系统无法响应其他事件。然而,很多设备(如网卡)在中断处理中需要完成大量工作(如拷贝数据包、协议栈处理)。为了解决这个矛盾,Linux 内核将中断处理拆分为两个阶段:上半部(Top Half) 和 下半部(Bottom Half)。

上半部(硬中断上下文)

  • 执行环境:在中断上下文中运行,不可睡眠,不可阻塞。
  • 职责:保存硬件状态、清除设备中断标志、将数据从设备 FIFO 拷贝到内存缓冲区、调度下半部。
  • 特点:必须极快完成(通常微秒级),否则会导致中断丢失或系统延迟飙升。

下半部(软中断 / tasklet / workqueue)

  • 执行环境:在软中断上下文或进程上下文中运行,部分实现可以睡眠(workqueue)。
  • 职责:完成上半部剩余的耗时工作,如协议栈处理、数据分发、唤醒用户态进程。
  • 机制:Linux 提供三种下半部实现机制:软中断(SoftIRQ)、tasklet(基于软中断构建)、工作队列(workqueue)。
性能陷阱:如果上半部执行时间过长,会直接增加中断延迟,导致网络吞吐量下降。在网卡场景中,过多的硬中断还会引发 中断风暴,使 CPU 100% 忙于处理中断而无法执行用户进程。这正是后续章节中通过 irqbalance 和 CPU 绑定来优化中断分布的根本原因。
中断处理流程:上半部与下半部 中断处理流程:上半部与下半部 设备触发中断 中断控制器(APIC) CPU 保存上下文 上半部:清除中断标志,拷贝数据 调度下半部(raise_softirq) 下半部:协议栈处理 / 数据分发 唤醒用户态进程(如 epoll) 恢复上下文,返回用户态 关键点: 上半部在中断上下文中执行,不可睡眠;下半部在软中断或进程上下文中执行,可被更高优先级中断抢占。 网络收包路径中,NAPI 机制通过轮询(poll)替代传统中断,减少上半部开销。

1.4 中断处理中的关键数据结构

Linux 内核使用 irq_desc 结构体描述每个中断号的状态和处理函数链。每个 irq_desc 包含中断状态标志、硬件控制器操作函数集(struct irq_chip)、以及一个或多个 irqaction 实例,后者保存了用户注册的中断处理函数(上半部)。

// 内核源码 include/linux/irqdesc.h (简化)
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_chip         *chip;        // 硬件控制器操作
    struct irqaction        *action;      // 中断处理函数链表
    unsigned int            status_use_accessors;
    unsigned int            depth;        // 嵌套深度
    ...
};

// 用户注册中断处理函数
// 驱动调用 request_irq(irq, handler, flags, name, dev)
// 内核内部创建 irqaction 实例并挂入 irq_desc->action 链表
实战提示:通过 /proc/interrupts 可以查看每个 CPU 上每个中断号的发生次数。这是后续分析中断亲和性是否生效的第一手工具。

1.5 中断亲和性的概念引入

在多核系统中,每个中断可以被路由到特定的 CPU 核心进行处理。中断亲和性(IRQ Affinity)决定了哪个 CPU 核心负责处理某个中断。默认情况下,中断可能被分配到任意核心,导致 CPU 缓存抖动和跨 NUMA 访问延迟。通过设置 /proc/irq/<IRQ>/smp_affinity 或使用 irqbalance 守护进程,可以显式控制中断的 CPU 分布,从而提升网络吞吐量和降低延迟。

本章后续内容将深入讲解如何查看当前中断分布、如何手动绑定中断到指定 CPU,以及 irqbalance 的自动均衡策略。