Linux 内核硬件中断机制深度实战:从 IRQ 到 softirq,AI 推理场景的硬中断延迟治理


一、问题的提出:为什么 AI 推理系统要关注硬中断?

大多数后端工程师对"中断"的理解停留在"硬件通知 CPU 有事要做"的抽象层面。但当你把一个 LLM 推理服务部署在单节点 8×GPU 的机型上时,网络 P99 延迟从 2ms 忽然飙升到 47ms,CPU 利用率看似不高,IO 也在水位线以下——top 里却多出一个 ksoftirqd 内核线程异常活跃。这时候,"中断"就是你需要直面的第一嫌疑人。

本文不是 API 文档复读,而是一次从硬件引脚到内核调度器、从 ftrace 到 eBPF 观测、从理论到生产实践的完整硬中断机制拆解。我们将回答三个核心问题:

  1. 一次网卡数据包到达后,CPU 到底经历了哪些执行阶段?
  2. local_irq_disable() 和 local_bh_disable() 在 x86-64 和 ARM64 上分别对应什么指令?代价差多少?
  3. AI 推理 高速网络(RoCE/RDMA NVMe-oF)场景下,如何系统性治理软中断风暴?

  4. 二、硬件中断的解剖学:从 APIC 到 irq_desc

    2.1 中断的物理路径

    当网卡收到一个数据包,它通过 PCIe Memory Write Transaction 把描述符写入 RX Ring,然后通过 MSI-X 向目标 CPU 的 Local APIC 发送一个中断消息。在 x86-64 上,这对应以下硬件级步骤:

    
    1. 网卡 → PCIe TLP (MSI-X write) → IOMMU 重定向 (若启用 VT-d)
    2. IOMMU → LAPIC ← 中断向量号   目标 CPU APIC ID
    3. LAPIC 在目标 CPU 上触发 INTR 引脚
    4. CPU 在指令边界检查中断(RFLAGS.IF=1 时)
    5. CPU 从中断向量号索引 IDT → 门描述符 → 跳转到 irq_entries_start
    

    关键细节:中断并不直接打断指令执行,而是在当前指令完成后、下一条指令之前才响应。对于乱序执行的现代 CPU,这个边界检查的频率大约是每字节指令的间隙。

    2.2 内核中断描述符:irq_desc 结构

    每个中断向量在内核中对应一个 struct irq_desc(定义于 kernel/irq/irqdesc.c),它包含了中断处理的核心元信息:

    
    struct irq_desc {
        struct irq_common_data  irq_common_data;
        struct irq_data         irq_data;
        unsigned int __percpu   *kstat_irqs;
        irq_flow_handler_t      handle_irq;    // 流处理层(如 handle_level_irq)
        struct irqaction        *action;       // 驱动注册的 handler 链表
        unsigned int            depth;         // disable 嵌套计数
        unsigned int            irq_count;     // 当前处理中计数(防止重入)
        unsigned long           last_unhandled;
        unsigned int            irqs_unhandled;
        raw_spinlock_t          lock;
        const char              *name;         // /proc/interrupts 中显示的名字
    };
    

    irqaction 是链表结构——多个设备可以共享同一个中断线(如多队列网卡的不同向量),每个驱动注册自己的 handler、dev_id、flags(IRQF_SHARED)。

    2.3 IDT 与 irq_entries_start

    在 arch/x86/kernel/idt.c 中,中断描述符表(IDT)被设置为一组门描述符。值得注意的是,Linux 使用中断门(Interrupt Gate)而非陷阱门——关键在于中断门会自动清除 EFLAGS.IF 位,也就是 x86 上硬件自动关中断(NMI 和某些异常依然能触发)。这是为什么 ISR(中断服务例程)默认运行在关中断状态——必须尽快完成或转交 softirq。

    
    // arch/x86/entry/entry_64.S 简化逻辑
    SYM_CODE_START(irq_entries_start)
        // 向量号通过宏迭代生成,vector 0x20 到 0xff
        .rept 0xe0  // 224 个外部中断向量
        UNWIND_HINT_IRET_REGS
        pushq $(\rst - irq_entries_start)    // 异常帧
        jmp interrupt_entry                 // C 入口
        .endr
    SYM_CODE_END(irq_entries_start)
    

    interrupt_entry 最终调用 dispatch_external_irq() → apic_eoi() → handle_irq() → ISR 链表调用,然后再进入 irq_exit() 按需调度 softirq。


    三、上半部与下半部(Top Half / Bottom Half)的本质划分

    3.1 设计动因

    如果所有工作都在 ISR 中做(IRQF_DISABLED 标志后已移除,标志着 Linux 全面转向下半部模型),那么系统会面临:

    • 中断遮蔽时间过长:IF=0 期间,同级/低级中断无法响应
    • 实时性劣化:调度器 tick、时钟中断被屏蔽
    • 延迟波动剧烈:ISR 执行时间直接贡献到尾延迟

    因此 Linux 采用 Top Half(关中断) Bottom Half(开中断、可延迟) 的经典分层。

    3.2 下半部机制的演进史

    机制 引入版本 特点 当代地位
    BH (Bottom Halves) 早期 全局32个静态链表 已废弃
    Tasklet 2.3 基于 softirq 的 per-CPU 链表,同类型不并发 受限使用
    softirq 2.3 32种静态,同类型可在不同CPU并发 核心机制
    Workqueue 2.5 可睡眠、可调度、运行在进程上下文 广泛使用
    Threaded IRQ 2.6.30 kthread 包装 ISR,可睡眠 生产推荐

    现代内核(6.x)中活跃的 softirq 类型只有 10 种:

    
    // include/linux/interrupt.h
    enum {
        HI_SOFTIRQ = 0,        // 高优先级 tasklet
        TIMER_SOFTIRQ,         // 定时器 (hrtimer 退化时)
        NET_TX_SOFTIRQ,        // 网络发送
        NET_RX_SOFTIRQ,        // 网络接收 ← AI 推理的关键瓶颈
        BLOCK_SOFTIRQ,         // 块层 completions (bio)
        IRQ_POLL_SOFTIRQ,      // NAPI poll 回退
        TASKLET_SOFTIRQ,       // 通用 tasklet
        SCHED_SOFTIRQ,         // CFS 负载均衡
        HRTIMER_SOFTIRQ,       // 高精度定时器
        RCU_SOFTIRQ,           // RCU 回调执行
        NR_SOFTIRQS
    };
    

    3.3 softirq 的完整调度链路

    一次 RX 数据包在 NAPI poll 中的完整执行路径:

    
    hardirq (IRQ handler)
      └── napi_schedule()           // 关闭设备中断,触发 softirq
          └── raise_softirq_irqoff(NET_RX_SOFTIRQ)
              └── __raise_softirq_irqoff()
                  └── 设置 CPU 的 softirq pending 位图 (local_cpu_offset)
    
    ── 下一个 irq_exit() 或local_bh_enable() 检查点 ──
    
    do_softirq()
      └── __do_softirq()
          └── h-                        
                        
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ .skip-link { position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } .skip-link:focus { top: 0; outline: 3px solid #0056b3; }