ARM64 上一个核挂了中断可以迁移到其他核吗
结论
分两种截然不同的情况,答案不同:
| 场景 | 能否迁移 | 机制 |
|---|---|---|
| 正常 CPU hotplug 下线(CPU 还活着、能执行指令) | ✅ 可以 | 软件主动迁移:irq_migrate_all_off_this_cpu() |
| 核挂死(不执行指令,如 hard lockup / 断电 / 死循环关中断) | ⚠️ 基本不能(软件路径失效,硬件路由仍指向死核) | 依赖硬件路由 + 活核软件介入,且 PPI 物理上不可迁移 |
一、正常 hotplug:软件主动迁移(可迁移)
被下线 CPU 自己执行 __cpu_disable()(运行在被下线核上):
arch/arm64/kernel/smp.c:302-327
__cpu_disable()
├── set_cpu_online(cpu, false)
├── ipi_teardown(cpu)
└── irq_migrate_all_off_this_cpu()
irq_migrate_all_off_this_cpu()(kernel/irq/cpuhotplug.c:171)遍历所有 active IRQ,逐个调用 migrate_one_irq()(kernel/irq/cpuhotplug.c:53):
- 跳过 per-cpu 中断(
irqd_is_per_cpu,即 PPI/SGI 类); - 对普通中断(SPI),用当前 affinity 掩码 ∩ cpu_online_mask 选新目标;若 affinity 里没有在线核,则强制改到
cpu_online_mask(brokeaff = true); - 调用
chip->irq_set_affinity。
GICv3 的实现是 gic_set_affinity()(drivers/irqchip/irq-gic-v3.c:1769):
cpu = cpumask_any_and(mask_val, cpu_online_mask);
...
offset = convert_offset_index(d, GICD_IROUTER, &index);
reg = gic_dist_base(d) + offset + (index * 8);
val = gic_cpu_to_affinity(cpu);
gic_write_irouter(val, reg); /* 重写中断路由寄存器 */
即:重写 GICD_IROUTERn,把该 SPI 的路由从下线核改指到在线核。之后中断直接由新目标核接收。注意这个流程的前提是 CPU 还活着——它要在自己身上执行完这套代码。
二、核挂死(不执行指令):迁移基本失效
如果核真的挂死(不取指、不响应中断、不响应 IPI),__cpu_disable() 永远执行不到,软件迁移路径直接失效。此时能否”迁移”取决于三层:
1. SPI(共享外设中断):路由仍指向死核,除非活核介入
- GIC 分发器里的
GICD_IROUTERn仍把该中断路由到死核(初始化时所有 SPI 都指向 boot CPU,之后由 affinity 设置改写); - 死核不响应 → 中断卡死:
- 电平触发:中断一直 pending(没人 ACK/EOI),GIC 认为还在处理中,其他核收不到;
- 边沿触发:可能直接丢失;
- 想让中断”迁移”,必须由某个活核上的软件重新写
GICD_IROUTER(如手动改/proc/irq/N/smp_affinity、或某个 CPU 死亡检测机制触发迁移)。Linux 通用代码没有针对”CPU 异常死亡”的自动中断迁移路径——这就是为什么 arm64 用 NMI watchdog 检测 hard lockup 后直接 panic,而不是尝试带病运行。
2. PPI(私有外设中断):物理上不可迁移
- PPI(INTID 16–31,如 arch timer、PMU)绑定在每个核自己的 Redistributor(GICR) 上,
gic_set_affinity()对gic_irq_in_rdist(d)直接返回-EINVAL(见上面代码); - 核挂死 → 该核的 PPI 中断永远没人处理(如 tick 停摆、PMU 采样停摆);
- 唯一的”软迁移”是 Linux 的 tick broadcast / broadcast timer 机制——但这针对的是 idle 深度睡眠核,由其他核代为发送 tick IPI;对挂死核不适用(它连 IPI 都不响应)。
3. SGI / IPI:死等
- 发给死核的 SGI 无人接收。调用方(如
smp_call_function_single、on_each_cpu)如果带等待语义,会永久阻塞,造成活锁/死锁。
三、硬件层面的”多目标路由”例外
GICv3 的 GICD_IROUTERn 支持把 SPI 配置为多 PE 目标:
- 1-of-N 模式:中断发给目标集合中的任意一个核;
- N-of-N 模式:广播给所有目标核,每个都收到。
若驱动/平台把中断配成多目标(如 irq_set_affinity 指定多个 CPU),即使其中一个目标挂死,其他目标仍能收到并处理——这是唯一不依赖软件迁移的硬件级”容错”。但 Linux 默认对普通 SPI 使用单目标路由,gic_set_affinity() 中 cpumask_any_and 也只选一个核。
四、与 TLBI 的联系(呼应你上一个问题)
死核对系统最大的威胁不是”中断没人处理”,而是破坏一致性协议:
| 机制 | 依赖 | 死核后果 |
|---|---|---|
broadcast TLBI(vmalle1is 等) |
所有核响应 DVM 广播并完成本地刷 TLB | 死核不响应 → TLBI 无法确认完成(DSB ISH 语义被破坏)→ 该核可能残留 stale TLB,后续引用已释放内存 |
IPI(smp_call_function) |
目标核响应并回 ACK | 死等 → 活锁 |
| 中断路由 | GIC 分发器 | SPI 卡死/丢失,PPI 永久无人处理 |
这正是 arm64 社区的一贯态度:宁可 panic 整个系统(NMI watchdog / pseudo-NMI),也不允许在”某核不响应”的状态下继续运行——因为任何需要全核参与的同步原语(TLBI、IPI、fence)都会在死核处断链,静默运行只会积累内存一致性错误。
五、工程建议(OLK 分支视角)
- 区分”软件下线”与”硬件死亡”:
echo 0 > /sys/devices/system/cpu/cpuN/online是安全路径,中断迁移由irq_migrate_all_off_this_cpu保证;若用 cpuset 隔离 +isolcpus,隔离核上的中断需要手工smp_affinity或irqbalance处理。 - 若需要”挂死核不 panic 系统”:必须有硬件级保证(如 PSCI CPU_OFF 之前该核已完成退出、且 GIC 路由已改指),否则应触发 panic——这与 upstream 的做法一致。
- 关注 GICv3 的 1-of-N 配置:对关键中断(如存储/网络控制器)可评估多目标路由,但注意
gic_set_affinity当前只选单核,需要厂商补丁或平台驱动配合。
参考代码位置(OLK-6.6 基线 c98e10d93310):
arch/arm64/kernel/smp.c:302-327—__cpu_disable()kernel/irq/cpuhotplug.c:171-189—irq_migrate_all_off_this_cpu()kernel/irq/cpuhotplug.c:53-159—migrate_one_irq()drivers/irqchip/irq-gic-v3.c:1769-1810—gic_set_affinity()(PPI 直接-EINVAL,SPI 重写GICD_IROUTER)drivers/irqchip/irq-gic-v3.c:1279-1282— 初始化时所有 SPI 默认路由到 boot CPU参考来源
- 来源: https://lwn.net/Articles/768418/
本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。