Fork me on GitHub
侧边栏

IPI与CPU唤醒机制

1. 核心关系

IPI(Inter-Processor Interrupt,处理器间中断)是一个 CPU 通知或唤醒另一个 CPU 的重要机制,但 CPU 唤醒不一定都由 IPI 引起。

可以把两者关系概括为:

发送 CPU
   │
   │ 发送 IPI
   ▼
中断控制器(ARM GIC / x86 APIC)
   │
   ├─ 目标 CPU 正在运行
   │      └─ 立即进入 IPI 中断处理函数
   │
   ├─ 目标 CPU 处于浅度 idle
   │      └─ IPI 触发退出 WFI/HLT,然后处理中断
   │
   └─ 目标 CPU 处于深度掉电
          └─ 需要电源管理机制先恢复 CPU,
             IPI 通常不能单独完成完整上电过程

因此:

  • CPU 正在运行时:IPI 只是一个普通的跨 CPU 中断。
  • CPU 处于浅度 idle 时:IPI 同时具有唤醒 CPU 和传递事件的作用。
  • CPU 处于深度掉电时:通常需要电源控制器、GIC 唤醒机制和固件配合,不能简单理解为"IPI 直接把 CPU 上电"。

2. CPU 的几种状态

讨论 CPU 唤醒与 IPI 的关系时,要先区分 CPU 当前处于什么状态。

2.1 CPU 正在运行

目标 CPU 正常执行任务:

CPU0                    CPU1
 │                       │
 │────── Send IPI ──────>│
 │                       │ 保存现场
 │                       │ 进入 IPI handler
 │                       │ 执行对应操作
 │                       │ 返回原任务

这种情况下并不存在真正的电源状态切换,只是 CPU1 响应了一个中断。

2.2 CPU 处于浅度 idle

ARM CPU 空闲时通常会执行:

wfi

WFI 表示 Wait For Interrupt。

x86 上类似的是:

hlt

此时:

  • CPU 不再执行普通指令;
  • 时钟可能被门控;
  • CPU 核心仍保持必要的硬件状态;
  • 可唤醒中断到达后,CPU 退出 WFI/HLT。

如果 CPU0 向 CPU1 发送 IPI:

CPU0                     CPU1
 │                        │
 │                        │ WFI
 │────── IPI ────────────>│
 │                        │ 退出 WFI
 │                        │ 恢复时钟
 │                        │ 处理中断
 │                        │ 执行调度等操作

在这种场景里,可以明确地说:

IPI 是目标 CPU 从 idle 状态退出的唤醒源。

2.3 CPU 处于深度 idle 或掉电状态

例如 ARM 平台上的:

  • CPU retention;
  • CPU power collapse;
  • cluster power collapse;
  • PSCI CPU suspend;
  • SoC 深度低功耗状态。

此时 CPU 核心可能发生:

  • CPU 时钟关闭;
  • CPU 电源域关闭;
  • 私有缓存失效;
  • GIC CPU interface 状态保存;
  • CPU 上下文由内核或固件保存;
  • CPU 无法直接响应普通中断。

唤醒流程通常类似:

IPI/Timer/外设中断
        │
        ▼
Always-on 中断控制器或 GIC
        │
        ▼
电源管理控制器检测到唤醒事件
        │
        ▼
恢复 CPU/Cluster 电源和时钟
        │
        ▼
固件恢复 CPU 上下文
        │
        ▼
Linux cpuidle resume
        │
        ▼
处理中断或执行调度

这时更准确的说法是:

IPI 可以作为唤醒事件,但 CPU 的实际恢复由中断控制器、电源管理控制器和固件共同完成。

能否通过 IPI 唤醒,取决于:

  • 当前 idle state 是否允许 IPI 作为唤醒源;
  • GIC/APIC 是否仍能接收并保存该中断;
  • 电源控制器是否配置了对应的 wakeup path;
  • CPU 是否只是 suspend,而不是被 hotplug 完全下线。

3. Linux 中 IPI 的主要用途

IPI 不只是用于唤醒,Linux 会使用 IPI 完成多种跨 CPU 操作。

3.1 调度唤醒与 Reschedule IPI

假设任务 A 可以在 CPU1 上运行,但 CPU1 当前处于 idle 状态。CPU0 唤醒任务 A 时,调度器可能会把任务放入 CPU1 的 runqueue,并向 CPU1 发送 reschedule IPI。

基本流程:

CPU0 唤醒 Task A
        │
        ▼
选择 CPU1 作为目标 CPU
        │
        ▼
把 Task A 加入 CPU1 的 runqueue
        │
        ▼
判断 CPU1 是否需要重新调度
        │
        ▼
发送 Reschedule IPI
        │
        ▼
CPU1 退出 idle
        │
        ▼
执行 schedule()
        │
        ▼
Task A 开始运行

常见相关函数包括:

函数 作用
try_to_wake_up() 将任务从睡眠状态转换为可运行状态
select_task_rq() 选择任务应进入哪个 CPU 的运行队列
ttwu_queue() 将被唤醒任务加入目标 CPU 的 runqueue
ttwu_queue_wakelist() 通过目标 CPU 的 wake list 延迟处理跨 CPU 唤醒
smp_send_reschedule() 向目标 CPU 发送重新调度 IPI

注意wake_up_process() 唤醒的是任务,不等于一定发送 IPI。只有在目标任务被放到其他 CPU,并且该 CPU 需要立即重新调度或退出 idle 时,才可能发送 reschedule IPI。

3.2 Call Function IPI

当一个 CPU 需要让其他 CPU 执行函数时,可以使用:

smp_call_function()
smp_call_function_single()
on_each_cpu()

典型流程:

CPU0
 │
 │ smp_call_function_single(CPU1, func, info, wait)
 │
 ├─ 将 func 放入 CPU1 的调用队列
 │
 ├─ 发送 Call Function IPI
 │
 ▼
CPU1
 │
 ├─ 响应 IPI
 │
 ├─ 取出 func
 │
 └─ 执行 func(info)
函数 作用
smp_call_function() 请求其他在线 CPU 执行指定函数
smp_call_function_single() 请求指定的一个 CPU 执行函数
on_each_cpu() 在所有在线 CPU 上执行函数

常见使用场景:

  • 更新每 CPU 状态;
  • 修改 MMU 或缓存状态;
  • CPU 频率和电源管理同步;
  • 调试和性能统计;
  • 跨 CPU 强制执行回调。

3.3 TLB Shootdown IPI

当一个 CPU 修改页表后,其他 CPU 的 TLB 中可能仍有旧映射,因此需要通知其他 CPU 刷新 TLB。

流程:

CPU0 修改页表
      │
      ▼
确定哪些 CPU 运行过该 mm
      │
      ▼
向相关 CPU 发送 TLB IPI
      │
      ▼
目标 CPU 执行本地 TLB invalidate

常见相关函数:

  • flush_tlb_mm()
  • flush_tlb_range()
  • flush_tlb_kernel_range()

这类 IPI 可能导致多个 CPU 同时被唤醒,因此频繁修改页表可能带来:

  • IPI 数量增加;
  • CPU idle residency 降低;
  • 系统功耗升高;
  • 多核扩展性下降。

3.4 CPU Stop IPI

用于停止目标 CPU,例如:

  • Kernel panic;
  • CPU hotplug;
  • 系统重启;
  • Crash dump;
  • 停止其他 CPU。

相关接口:

  • smp_send_stop()
  • stop_machine()

3.5 IRQ Work IPI

某个 CPU 可以向另一个 CPU 提交 irq work,并通过 IPI 让目标 CPU 尽快处理。

相关接口:

irq_work_queue_on()

常见用途:

  • tracing;
  • perf;
  • lockup detector;
  • 异步跨 CPU 操作。

4. IPI 与任务唤醒不是同一个概念

这是分析调度问题时最容易混淆的地方。

4.1 任务唤醒

任务唤醒指的是:

TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE
                │
                ▼
          TASK_RUNNING

例如:

wake_up_process(task);

它改变的是任务状态,并将任务放入 runqueue。

4.2 CPU 唤醒

CPU 唤醒指的是:

CPU idle/WFI
      │
      ▼
CPU active

它改变的是 CPU 的运行或电源状态

4.3 IPI

IPI 是 CPU 之间的通知机制:

CPU0 ──中断消息──> CPU1

4.4 三者的关系

常见的完整关联链路为:

任务被唤醒
    │
    ▼
任务进入远端 CPU 的 runqueue
    │
    ▼
发送 Reschedule IPI
    │
    ▼
远端 CPU 退出 idle
    │
    ▼
远端 CPU 调度该任务

但也可能没有 IPI:

任务被唤醒
    │
    ▼
任务进入当前 CPU 的 runqueue
    │
    ▼
当前 CPU 在后续调度点运行该任务

或者:

任务进入远端 CPU runqueue
    │
    ▼
目标 CPU 本来就在运行
    │
    ▼
通过调度标志触发后续调度

是否发送 IPI 由调度器根据目标 CPU 状态、任务优先级和调度时机决定。

5. need_resched 与 Reschedule IPI 的关系

Linux 调度器通常会给目标 CPU 设置:

TIF_NEED_RESCHED

表示该 CPU 需要重新调度。

如果是当前 CPU:

设置 need_resched
    │
    ▼
当前 CPU 在中断返回、系统调用返回或抢占点调用 schedule()

如果是远端 CPU:

设置目标 CPU 的 need_resched
    │
    ▼
发送 Reschedule IPI
    │
    ▼
目标 CPU 立即感知调度请求

概念上可以理解为:

need_resched = 状态标志
IPI          = 通知目标 CPU 尽快检查该标志

只设置标志但不通知目标 CPU,目标 CPU 可能要等到下一个自然调度点才处理;如果目标 CPU 正在 WFI,则可能需要 IPI 让其退出 idle。

6. ARM GIC 中 IPI 的实现

在 ARM64 系统中,IPI 通常通过 GIC 的 SGI(Software Generated Interrupt)实现。

Linux IPI
   │
   ▼
ARM64 arch_send_call_function_ipi_mask()
   │
   ▼
GIC 发送 SGI
   │
   ▼
目标 CPU 收到中断
   │
   ▼
handle_IPI()

SGI 通常属于 GIC 的中断 ID:

INTID 0~15

Linux 会在软件层将不同功能映射到不同的 IPI message,例如:

  • Reschedule;
  • Call function;
  • CPU stop;
  • Timer broadcast;
  • IRQ work;
  • CPU backtrace。

需要区分 Linux IPI message numberGIC hardware SGI INTID,两者不一定是简单的一一等价关系,具体取决于内核版本及架构实现。

7. CPU Offline 与 Idle 的区别

7.1 Idle CPU

Idle CPU 仍然是 online:

/sys/devices/system/cpu/cpuX/online = 1

它可以:

  • 接收 IPI;
  • 被调度器分配任务;
  • 从 WFI 或 idle state 中唤醒;
  • 处理定时器和中断。

7.2 Offline CPU

Offline CPU 已被 CPU hotplug 下线:

/sys/devices/system/cpu/cpuX/online = 0

它通常不能:

  • 接收普通调度 IPI;
  • 被分配普通任务;
  • 像 idle CPU 一样由 reschedule IPI 唤醒。

重新上线通常需要:

echo 1 > /sys/devices/system/cpu/cpuX/online

内核会经过 CPU hotplug 和架构 boot 流程,ARM 平台通常还会调用 PSCI CPU_ON。

IPI 可以唤醒 idle CPU,但不能代替 CPU hotplug 流程重新启动 offline CPU。

8. 调试与确认方法

8.1 查看 IPI 统计

cat /proc/interrupts

ARM64 上可能看到:

IPI0:  Rescheduling interrupts
IPI1:  Function call interrupts
IPI2:  CPU stop interrupts
IPI3:  CPU stop (for crash dump) interrupts
IPI4:  Timer broadcast interrupts
IPI5:  IRQ work interrupts
IPI6:  CPU wake-up interrupts

持续观察:

watch -n 1 'cat /proc/interrupts | grep -E "IPI|RES|CAL|TLB"'

8.2 使用 ftrace 查看任务唤醒和调度

cd /sys/kernel/tracing

echo 0 > tracing_on
echo > trace

echo 1 > events/sched/sched_waking/enable
echo 1 > events/sched/sched_wakeup/enable
echo 1 > events/sched/sched_switch/enable
echo 1 > events/power/cpu_idle/enable

echo 1 > tracing_on
sleep 5
echo 0 > tracing_on

cat trace
事件 作用
sched_waking 某个任务开始进入唤醒流程
sched_wakeup 任务已进入可运行状态
sched_switch CPU 发生任务切换
cpu_idle CPU 进入或退出 idle state

分析顺序:

sched_waking
    ↓
sched_wakeup
    ↓
目标 CPU 退出 cpu_idle
    ↓
sched_switch 到被唤醒任务

如果目标 CPU 在 sched_wakeup 后立即退出 idle,通常表明跨 CPU 唤醒触发了 reschedule IPI 或其他唤醒事件。

8.3 跟踪 IPI

部分 ARM64 内核支持 IPI tracepoint,可以先检查:

find /sys/kernel/tracing/events -maxdepth 2 -type d | grep -i ipi

常见事件包括:

  • ipi/ipi_raise
  • ipi/ipi_entry
  • ipi/ipi_exit

启用方式:

cd /sys/kernel/tracing

echo 1 > events/ipi/ipi_raise/enable
echo 1 > events/ipi/ipi_entry/enable
echo 1 > events/ipi/ipi_exit/enable
事件 作用
ipi_raise 源 CPU 向目标 CPU 发起 IPI
ipi_entry 目标 CPU 开始处理 IPI
ipi_exit 目标 CPU 完成 IPI 处理

将其与调度事件、idle 事件一起跟踪,可以还原完整链路:

CPU0: sched_waking
CPU0: sched_wakeup
CPU0: ipi_raise                 target=CPU1
CPU1: cpu_idle                  state=-1,退出 idle
CPU1: ipi_entry                 Rescheduling
CPU1: ipi_exit
CPU1: sched_switch              idle -> target_task

并非所有厂商内核都启用了这些 tracepoint。如果没有,可以使用 /proc/interrupts、函数跟踪或 Perfetto 辅助判断。

9. 总结

场景 IPI 是否唤醒 CPU 说明
CPU 正在运行 只触发跨 CPU 中断处理
CPU 处于 WFI/浅度 idle IPI 使 CPU 退出 idle
CPU retention 通常可以 取决于 GIC 和平台电源配置
CPU power collapse 可能可以 需要 GIC、电源控制器和固件配合
CPU offline 不可以直接唤醒 需要 CPU hotplug/PSCI CPU_ON 流程
本地任务唤醒 通常不需要 IPI 当前 CPU 自己处理
远端任务唤醒 可能发送 IPI 取决于目标 CPU 状态和调度器判断

最核心的逻辑是:

任务唤醒 != CPU 唤醒 != IPI

任务唤醒:让任务变为 TASK_RUNNING
CPU 唤醒:让 CPU 从 idle/低功耗状态恢复运行
IPI:一个 CPU 通知另一个 CPU 的中断机制

常见的完整关联链路为:

CPU0 唤醒任务
→ 任务进入 CPU1 runqueue
→ CPU0 向 CPU1 发送 Reschedule IPI
→ CPU1 退出 WFI/idle
→ CPU1 处理中断并重新调度
→ 被唤醒任务在 CPU1 上运行
posted @ 2026-07-15 10:13  yooooooo  阅读(2)  评论(0)    收藏  举报