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 number 和 GIC 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_raiseipi/ipi_entryipi/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 上运行


浙公网安备 33010602011771号