跳转至

第五篇:CPU Hotplug、MSI 与电源管理 — IRQ 的跨 CPU 迁移与睡眠唤醒

源码:kernel/irq/cpuhotplug.c kernel/irq/msi.c kernel/irq/pm.c | 头文件:include/linux/irqflags.h

系列目录:IRQ 内核源码深度解析


1. 概述

前四篇假设了稳态运行。现实中系统会遇到:

  • CPU 热插拔:CPU offline 时挂在上面的 IRQ 怎么办?
  • MSI 中断:现代 PCIe 设备使用 MSI/MSI-X 替代 INTx,内核如何描述?
  • 系统睡眠/唤醒:suspend to RAM 期间中断如何处理?唤醒后如何恢复?

本文聚焦这三个高级话题。


2. 中断上下文开关 (irqflags.h:200-212, 237-238)

// include/linux/irqflags.h:200-204 (CONFIG_TRACE_IRQFLAGS=y)
#define local_irq_enable()              \
    do {                                \
        trace_hardirqs_on();            \
        raw_local_irq_enable();         \
    } while (0)

// include/linux/irqflags.h:206-212 (CONFIG_TRACE_IRQFLAGS=y)
#define local_irq_disable()             \
    do {                                \
        bool was_disabled = raw_irqs_disabled(); \
        raw_local_irq_disable();        \
        if (!was_disabled)              \
            trace_hardirqs_off();       \
    } while (0)

// include/linux/irqflags.h:237-238 (CONFIG_TRACE_IRQFLAGS=n)
#define local_irq_enable()  do { raw_local_irq_enable(); } while (0)
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)

raw_local_irq_disable → arch_local_irq_disable: x86 → cli, arm64 → msr DAIFSet.


3. CPU Hotplug 与 IRQ 迁移

3.1 问题场景

   CPU2 offline:
   ┌────────┐     ┌────────┐     ┌────────┐     ┌────────┐
   │ CPU0   │     │ CPU1   │     │ CPU2   │     │ CPU3   │
   │ IRQ 32 │     │ IRQ 33 │     │ 离线 ↓  │     │ IRQ 35 │
   │ IRQ 36 │     │ IRQ 37 │     │ IRQ 34 │     │ IRQ 39 │
   └────────┘     └────────┘     └────┬───┘     └────────┘
                              irq_migrate_all
                              _off_this_cpu()
                              ┌──────┼──────┐
                              ▼      ▼      ▼
                           CPU0   CPU1   CPU3

3.2 irq_migrate_all_off_this_cpu (cpuhotplug.c:171)

// kernel/irq/cpuhotplug.c:171
void irq_migrate_all_off_this_cpu(void)
{
    struct irq_desc *desc;
    unsigned int irq;

    for_each_active_irq(irq) {          // 遍历所有活跃 IRQ
        bool affinity_broken;

        desc = irq_to_desc(irq);
        scoped_guard(raw_spinlock, &desc->lock) {
            affinity_broken = migrate_one_irq(desc);
            if (affinity_broken && desc->affinity_notify)
                irq_affinity_schedule_notify_work(desc);
        }
        if (affinity_broken)
            pr_debug_ratelimited("IRQ %u: no longer affine to CPU%u\n",
                                irq, smp_processor_id());
    }
}

3.3 migrate_one_irq — 核心迁移逻辑 (cpuhotplug.c:53)

// kernel/irq/cpuhotplug.c:53
static bool migrate_one_irq(struct irq_desc *desc)
{
    struct irq_data *d = irq_desc_get_irq_data(desc);
    struct irq_chip *chip = irq_data_get_irq_chip(d);
    bool maskchip = !irq_can_move_pcntxt(d) && !irqd_irq_masked(d);
    const struct cpumask *affinity;
    bool brokeaff = false;
    int err;

    // 1. chip 不存在或无 set_affinity → 无法迁移
    if (!chip || !chip->irq_set_affinity) {
        pr_debug("IRQ %u: Unable to migrate away\n", d->irq);
        return false;
    }

    // 2. 完成 pending 中断移动清理
    irq_force_complete_move(desc);

    // 3. per-CPU / 未启动 / 不需要修复 → 跳过
    if (irqd_is_per_cpu(d) || !irqd_is_started(d) ||
        !irq_needs_fixup(d)) {
        irq_fixup_move_pending(desc, false);
        return false;
    }

    // 4. 确定目标 CPU mask
    if (irq_fixup_move_pending(desc, true))
        affinity = irq_desc_get_pending_mask(desc);
    else
        affinity = irq_data_get_affinity_mask(d);

    // 5. 不能进程迁移的 IRQ 先 mask
    if (maskchip && chip->irq_mask)
        chip->irq_mask(d);

    // 6. affinity 与在线 CPU 无交集
    if (!cpumask_intersects(affinity, cpu_online_mask)) {
        if (irqd_affinity_is_managed(d)) {
            irqd_set_managed_shutdown(d);
            irq_shutdown_and_deactivate(desc);
            return false;
        }
        affinity = cpu_online_mask;
        brokeaff = true;
    }

    // 7. 执行迁移 (不强制,保留 offline CPU 屏蔽)
    err = irq_do_set_affinity(d, affinity, false);

    // 8. ENOSPC → 放宽到全部在线 CPU 重试
    if (err == -ENOSPC && !irqd_affinity_is_managed(d) &&
        affinity != cpu_online_mask) {
        affinity = cpu_online_mask;
        brokeaff = true;
        err = irq_do_set_affinity(d, affinity, false);
    }

    if (err) {
        pr_warn_ratelimited("IRQ%u: set affinity failed(%d).\n",
                           d->irq, err);
        brokeaff = false;
    }

    if (maskchip && chip->irq_unmask)
        chip->irq_unmask(d);

    return brokeaff;
}

决策树:

migrate_one_irq(desc)
 ├─ chip 不存在? → return false
 ├─ 完成 pending move cleanup
 ├─ per-CPU / !started / 不需要修复? → return false
 ├─ 确定目标 affinity mask
 ├─ 目标 ∩ online_CPU == ∅?
 │    ├─ Managed → shutdown, return false
 │    └─ 普通 → affinity=cpu_online_mask, brokeaff=true
 ├─ irq_do_set_affinity()
 │    └─ ENOSPC? → 放宽到 cpu_online_mask 重试
 └─ return brokeaff

3.4 Managed 中断

Managed 中断 (IRQD_AFFINITY_MANAGED) 由内核管理 affinity。典型场景:PCIe MSI/MSI-X 多队列网卡、IOMMU 设备中断。

Managed 中断保证:
  1. 内核自动选择目标 CPU
  2. CPU offline: 已启动 → 迁移; 已 shutdown → 等待 CPU online
  3. CPU online: irq_affinity_online_cpu() 恢复被 shutdown 的中断

3.5 CPU 上线:irq_affinity_online_cpu (cpuhotplug.c:233)

// kernel/irq/cpuhotplug.c:233
int irq_affinity_online_cpu(unsigned int cpu)
{
    struct irq_desc *desc;
    unsigned int irq;

    irq_lock_sparse();
    for_each_active_irq(irq) {
        desc = irq_to_desc(irq);
        scoped_guard(raw_spinlock_irq, &desc->lock)
            irq_restore_affinity_of_irq(desc, cpu);
    }
    irq_unlock_sparse();
    return 0;
}

irq_restore_affinity_of_irq (cpuhotplug.c:206):仅处理 managed 中断且新 CPU 在 affinity 范围内;如果之前是 managed_shutdown → 重新启动;执行 irq_set_affinity_locked 迁移。


4. MSI 中断基础设施

4.1 MSI vs INTx

传统 INTx:
  PCI 设备 → IO-APIC → CPU LAPIC
  共享物理线、4 条线限制、需轮询 ISR

MSI:
  PCIe 设备 ──内存写入(TLP)──→ CPU LAPIC
  无需 IO-APIC 中转、每向量独立、MSI-X 最多 2048 向量、更低的延迟

4.2 核心数据结构

struct msi_device_data (msi.c:32)

// kernel/irq/msi.c:32
struct msi_device_data {
    unsigned long           properties;
    struct mutex            mutex;
    struct msi_dev_domain   __domains[MSI_MAX_DEVICE_IRQDOMAINS];
    unsigned long           __iter_idx;
};

每个 MSI 设备通过 dev->msi.data 维护。__domains[] 支持一个设备与多个 MSI 域关联。

struct msi_ctrl (msi.c:47)

// kernel/irq/msi.c:47
struct msi_ctrl {
    unsigned int            domid;   // MSI 域 ID
    unsigned int            first;   // 硬件起始槽位
    unsigned int            last;    // 硬件结束槽位
    unsigned int            nirqs;   // Linux IRQ 数量 (可能 > 范围: PCI/multi-MSI)
};

msi_alloc_desc (msi.c:76)

// kernel/irq/msi.c:76
static struct msi_desc *msi_alloc_desc(struct device *dev, int nvec,
                                       const struct irq_affinity_desc *affinity)
{
    struct msi_desc *desc = kzalloc_obj(*desc);
    if (!desc) return NULL;

    desc->dev = dev;
    desc->nvec_used = nvec;
    if (affinity) {
        desc->affinity = kmemdup_array(affinity, nvec,
                                      sizeof(*desc->affinity), GFP_KERNEL);
        if (!desc->affinity) { kfree(desc); return NULL; }
    }
    return desc;
}

msi_insert_desc (msi.c:102)

描述符存储在 xarray 中,支持自动分配和指定索引两种模式。

4.3 MSI 整体架构

pci_alloc_irq_vectors()       ← PCI 层 API
msi_domain_alloc_irqs()       ← MSI 域层
   ┌───┼───┐
   ▼       ▼               ▼
msi_alloc  irq_domain     irq_create
_desc()    _alloc_irqs()  _fwspec_mapping()
   │                       │
   ▼                       ▼
                    struct irq_desc
                    irq_common_data.msi_desc → struct msi_desc

5. 中断电源管理 (IRQ PM)

5.1 关键标志

标志 来源 含义
IRQF_NO_SUSPEND pm.c:39 suspend 期间不禁用
IRQF_COND_SUSPEND pm.c:41 无其他用户时才 suspend
IRQF_FORCE_RESUME pm.c:33 resume 时强制恢复
IRQF_EARLY_RESUME pm.c:59 附近 设备 resume 前就在 syscore 阶段恢复

5.2 状态标志

标志 含义
IRQS_SUSPENDED (pm.c:96) 描述符已挂起
IRQD_WAKEUP_ARMED (pm.c:75) 此 IRQ 被设定为唤醒源
IRQD_IRQ_ENABLED_ON_SUSPEND (pm.c:85) suspend 期间被启用的唤醒中断

5.3 suspend_device_irqs (pm.c:126)

// kernel/irq/pm.c:126
void suspend_device_irqs(void)
{
    struct irq_desc *desc;
    int irq;

    for_each_irq_desc(irq, desc) {
        bool sync;
        if (irq_settings_is_nested_thread(desc))
            continue;
        scoped_guard(raw_spinlock_irqsave, &desc->lock)
            sync = suspend_device_irq(desc);
        if (sync)
            synchronize_irq(irq);
    }
}

单 IRQ 处理 suspend_device_irq (pm.c:65):

// kernel/irq/pm.c:65 (简化)
static bool suspend_device_irq(struct irq_desc *desc)
{
    // 跳过: 无 action、级联中断、no_suspend_depth > 0
    if (!desc->action || irq_desc_is_chained(desc) ||
        desc->no_suspend_depth)
        return false;

    // ★ 唤醒源处理
    if (irqd_is_wakeup_set(irqd)) {
        irqd_set(irqd, IRQD_WAKEUP_ARMED);
        if ((chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) &&
            irqd_irq_disabled(irqd)) {
            __enable_irq(desc);              // 保持唤醒源活跃
            irqd_set(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND);
        }
        return true;   // 需要 synchronize_irq
    }

    // ★ 非唤醒源处理
    desc->istate |= IRQS_SUSPENDED;
    __disable_irq(desc);
    if (chipflags & IRQCHIP_MASK_ON_SUSPEND)
        mask_irq(desc);   // chip 级 mask
    return true;
}

5.4 唤醒处理:irq_pm_handle_wakeup (pm.c:16)

// kernel/irq/pm.c:16
void irq_pm_handle_wakeup(struct irq_desc *desc)
{
    irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED);
    desc->istate |= IRQS_SUSPENDED | IRQS_PENDING;
    desc->depth++;
    irq_disable(desc);
    pm_system_irq_wakeup(irq_desc_get_irq(desc));   // 通知 PM 核心
}

5.5 resume_device_irqs (pm.c:246)

// kernel/irq/pm.c:246
void resume_device_irqs(void)
{
    resume_irqs(false);   // false = 非 early resume
}

恢复分两个阶段: 1. Early resume (syscore): irq_pm_syscore_resumeresume_irqs(true) — 仅恢复 IRQF_EARLY_RESUME 中断 2. Late resume: resume_device_irqsresume_irqs(false) — 恢复其余中断

单 IRQ 恢复 (pm.c:144):

// kernel/irq/pm.c:144
static void resume_irq(struct irq_desc *desc)
{
    irqd_clear(irqd, IRQD_WAKEUP_ARMED);

    if (irqd_is_enabled_on_suspend(irqd)) {
        __disable_irq(desc);   // suspend 期间启用的 → 恢复禁用
        irqd_clear(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND);
    }

    if (desc->istate & IRQS_SUSPENDED)
        goto resume;

    if (!desc->force_resume_depth)   // IRQF_FORCE_RESUME
        return;

    desc->depth++;
    irq_state_set_disabled(desc);
    irq_state_set_masked(desc);
resume:
    desc->istate &= ~IRQS_SUSPENDED;
    __enable_irq(desc);
}

5.6 rearm_wake_irq (pm.c:198)

// kernel/irq/pm.c:198
void rearm_wake_irq(unsigned int irq)
{
    scoped_irqdesc_get_and_buslock(irq, IRQ_GET_DESC_CHECK_GLOBAL) {
        struct irq_desc *desc = scoped_irqdesc;

        if (!(desc->istate & IRQS_SUSPENDED) ||
            !irqd_is_wakeup_set(&desc->irq_data))
            return;

        desc->istate &= ~IRQS_SUSPENDED;
        irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
        __enable_irq(desc);
    }
}

下次 suspend 前重新武装唤醒 IRQ。


6. 完整 PM 流程图

6.1 Suspend 流程

系统 Suspend
suspend_device_irqs()     pm.c:126
  ┌───┼───┐
  ▼   ▼   ▼
NO_    Wakeup   普通 IRQ
SUSPEND IRQ
  │      │         │
 跳过   ARM        IRQS_SUSPENDED
      保持unmask   __disable_irq()
  │      │         │
  │      │    mask_irq()
  │      │    (IRQCHIP_MASK_ON_SUSPEND)
  │      │         │
  └──────┴────┬────┘
      synchronize_irq()
      系统进入 Suspend
(仅 wakeup IRQ 仍在监听)
         唤醒事件 (RTC/WoL等)
      wakeup IRQ handler 触发
      → irq_pm_handle_wakeup()      pm.c:16
      → pm_system_irq_wakeup()

6.2 Resume 流程

系统 Resume
irq_pm_syscore_resume()   syscore 阶段,最早执行
  → resume_irqs(true)     仅恢复 IRQF_EARLY_RESUME 中断
设备驱动 resume
resume_device_irqs()      pm.c:246
  → resume_irqs(false)    恢复全部非 early 中断
  ┌───┼───┐
  ▼   ▼   ▼
 WAKEUP_  IRQS_      FORCE_
 ARMED    SUSPENDED  RESUME
  │         │          │
 清除ARMED 清除状态    强制启用
 如有ON_   __enable
 SUSPEND  _irq()
 则disable  │
  │         │          │
  └─────────┴────┬─────┘
          所有 IRQ 恢复正常

7. PM 状态变迁表

┌──────────────────────┬────────────────────────────┬──────────────────────────┐
│ 中断类型              │ Suspend 操作                │ Resume 操作               │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ IRQF_NO_SUSPEND      │ 跳过,保持活跃               │ 跳过,仍然活跃             │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ Wakeup + DISABLED    │ 启用 + ARM + ON_SUSPEND    │ 禁用 + CLEAR              │
│ (+ENABLE_ON_SUSPEND) │                            │                           │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ Wakeup + ENABLED     │ ARM (保持启用)              │ 清除 ARM                  │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ 普通 IRQ              │ SUSPENDED + disable        │ 清除 SUSPENDED + enable   │
│                      │ (+ MASK_ON_SUSPEND→mask)   │                           │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ IRQF_FORCE_RESUME    │ SUSPENDED + disable         │ 强制启用                   │
├──────────────────────┼────────────────────────────┼──────────────────────────┤
│ IRQF_EARLY_RESUME    │ (同上)                      │ Early phase (syscore) 恢复 │
└──────────────────────┴────────────────────────────┴──────────────────────────┘

8. 全景图

┌──────────────────────────────────────────────────────────────────────────────┐
│                        Linux IRQ 子系统全景                                    │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  request_irq ─► irq_desc ─► handle_irq_event ─► irq_exit ─► __do_softirq    │
│  第1篇             第1篇         第2篇               第4篇        第4篇        │
│                                                                              │
│  note_interrupt ─► /proc/interrupts ─► tasklet_action ─► ksoftirqd          │
│  第3篇              第3篇                 第4篇             第4篇             │
│                                                                              │
│  migrate_one_irq ─► managed IRQ ─► msi_alloc_desc ─► suspend_device_irqs    │
│  第5篇               第5篇           第5篇               第5篇                 │
│                                                                              │
│  ┌────────────────────┐                                                      │
│  │  同步子系统         │  spin_lock(_irqsave) / local_bh_disable / RCU       │
│  │  与中断紧密耦合      │  → 理解中断上下文后才能正确使用锁                     │
│  └────────────────────┘                                                      │
└──────────────────────────────────────────────────────────────────────────────┘

9. 实际场景示例

# CPU offline 跟踪
echo 0 > /sys/devices/system/cpu/cpu2/online
# 内核: take_cpu_down → fixup_irqs → irq_migrate_all_off_this_cpu
cat /proc/interrupts | awk '{print $1,$3,$4,$5,$6}'  # 查看 CPU2 列是否归零

# Suspend/Resume 跟踪
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo mem > /sys/power/state
# 唤醒后: 检查 trace 中的 handler 调用链

# MSI 信息
lspci -vvv | grep -A 20 "MSI"
cat /proc/interrupts | grep "PCI-MSI"

10. 系列结语

五篇文章完整覆盖 Linux 内核中断子系统的核心实现:

主题 核心文件 关键知识点
1 IRQ 描述符与 request_irq manage.c, irqdesc.c irq_desc 结构、ISR 注册生命周期、ONESHOT
2 Flow Handlers chip.c, handle.c level/edge/fasteoi 三种流控模式、EOI 顺序
3 /proc 与虚假中断 proc.c, spurious.c show_interrupts 统计、note_interrupt 检测
4 SoftIRQ 与 Tasklet softirq.c pending 位图、MAX_RESTART 机制、ksoftirqd
5 CPU Hotplug/MSI/PM cpuhotplug.c, msi.c, pm.c IRQ 迁移、MSI 架构、suspend/resume

知识图谱

┌─────────────────────────────────────────────────────────────────────────┐
│                    内核中断 + 同步 知识体系                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌────────────────────┐                                                 │
│  │  IRQ 中断子系统      │  ← 本系列五篇                                 │
│  │  ├─ 描述符 & 注册    │                                                │
│  │  ├─ Flow Handler    │                                               │
│  │  ├─ 统计 & 检测     │                                                │
│  │  ├─ SoftIRQ/Tasklet │                                               │
│  │  └─ Hotplug/MSI/PM  │                                               │
│  └─────────┬──────────┘                                                │
│            │ 配合使用                                                    │
│            ▼                                                            │
│  ┌────────────────────┐                                                 │
│  │  内核同步机制        │  spin_lock(&_irqsave) / rwlock / RCU / mutex  │
│  │                     │  理解中断上下文才能正确使用锁                     │
│  └────────────────────┘                                                 │
└─────────────────────────────────────────────────────────────────────────┘

推荐下一步

  1. 内核定时器 (kernel/time/): HRTIMER_SOFTIRQ 如何驱动高精度定时器
  2. NAPI 网络子系统 (net/core/dev.c): NET_RX_SOFTIRQ 如何实现 NAPI 轮询
  3. RCU 回调 (kernel/rcu/): RCU_SOFTIRQ 如何处理 RCU 回调
  4. 内核锁机制 (kernel/locking/): spin_lock 中断变体、local_bh_disable 与锁的配合
  5. 中断线程化 (kernel/irq/manage.c): IRQF_ONESHOT / force_irqthreads 实现

本系列所有源码引用基于 Linux kernel v7.1-rc5-26 (eb3f4b7426cf),行号以该版本为基准。


💬 评论