一、引言:多核协作为什么离不开 IPI
单核系统中,通知另一段代码执行依赖共享变量与调度器;多核系统中,各核拥有独立的流水线与中断使能状态,一个核修改共享状态后,其余核可能仍处于 WFI 休眠或运行在过期上下文中。核间中断(Inter-Processor Interrupt,IPI)正是打破这一僵局的标准硬件手段。
Linux 多核系统中 IPI 有三类高频用途:
- 重新调度:唤醒目标核上的更高优先级任务,任务迁移与定时器抢占路径均会触发;
- 跨核函数调用 :
smp_call_function系列接口让回调在目标核的上下文中执行,典型场景如每核统计刷新; - TLB 打靶:页表变更后使其他核失效本地 TLB 项,保证地址翻译的全局一致。
本系列第 15 篇中 SBI HSM 唤醒从核、第 17 篇中 SFENCE.VMA 的多核语义,背后都以 IPI 机制为支撑。本文沿硬件路径的演进顺序展开:从 CLINT 的 MSIP 寄存器出发,经 SBI 扩展的标准化封装,直至 AIA 架构下的消息式投递。
二、硬件起点:CLINT 与 MSIP 寄存器
IPI 最初的硬件载体是 CLINT 中的 MSIP(Machine Software Interrupt Pending)寄存器。每个 Hart 对应一个 32 位 MSIP:写入 1 将目标核 mip 寄存器的 MSIP 位置起,触发 M 模式软件中断;写入 0 清除挂起。
以 QEMU virt 平台为例,CLINT 基地址为 0x02000000,Hart N 的 MSIP 位于基址偏移 4N 处:
c
#define CLINT_BASE 0x02000000UL
#define MSIP(hart) (*(volatile uint32_t *)(CLINT_BASE + 4 * (hart)))
/* 向 Hart 1 发送核间中断 */
MSIP(1) = 1;
M 模式下这条内存写即完成一次 IPI 投递。S 模式则无权直接访问 CLINT:即便 mideleg 已将软件中断委托给 S 模式,SSIP 位的置位操作仍属 M 模式资源,S 模式必须经固件代理。
三、S 模式标准路径:SBI IPI 扩展
S 模式发送 IPI 的标准做法是 ecall 请求 M 模式固件代发。SBI 规范定义了 IPI 扩展(EID 0x735049),核心函数 sbi_send_ipi 以 Hart 位图描述目标集合:
c
struct sbiret sbi_send_ipi(unsigned long hart_mask,
unsigned long hart_mask_base);
hart_mask 的第 i 位对应 hart_mask_base + i 号 Hart。固件收到请求后逐核写入 MSIP 完成投递。
该路径的代价是一次 trap:S 模式陷入、M 模式转发、异常返回,延迟高于直接内存写;换来的是特权级隔离与实现无关性------无论底层是 CLINT 还是 IMSIC,内核代码保持一致。
与之配合的是 RFENCE 扩展(EID 0x52464E43):sbi_remote_sfence_vma 使固件在目标核上执行 TLB 失效,多核页表同步由此实现。早期内核的 TLB 打靶正是"IPI 通知 + 目标核本地 sfence"的固件封装。
四、AIA 时代:IMSIC 消息式 IPI
本系列第 19 篇介绍的 AIA 架构改变了 IPI 的投递方式。每核的 IMSIC 中断文件可按特权级独立映射,S 模式获得自己的文件页后,发送 IPI 不再需要陷入固件:直接向目标核中断文件的 seteipnum 寄存器写入中断标识,一次内存写即完成投递。
IPI 由此与设备 MSI 走上同一条消息通路:软件中断不再依赖 CLINT 这一独立设施,而是中断文件中的一个普通中断标识。在 AIA 平台上,Linux 中 IPI 的 irqchip 归属相应从 clint 切换为 imsics,可通过 /proc/interrupts 直接观察到这一变化。
三条路径对照如下:
| 对比维度 | CLINT MSIP 直写 | SBI IPI 扩展 | AIA IMSIC 消息 |
|---|---|---|---|
| 触发方式 | M 模式写 MSIP 寄存器 | S 模式 ecall,固件转发 | 向目标中断文件写 MSI |
| 适用特权级 | M 模式 | S / VS 模式 | 各特权级(含 VS 直通) |
| 投递开销 | 一次内存写 | trap + 固件转发 | 一次内存写 |
| 典型使用者 | 固件、裸机程序 | 传统平台 Linux | AIA 平台 Linux |
五、Linux 视角:IPI 的三大用途与观察方法
三类用途在内核中各有独立入口:重新调度由 smp_send_reschedule 发出;跨核函数调用经 smp_call_function 族接口;TLB 打靶在页表变更路径上触发,区间较大时一次广播覆盖全部在线核。
观察方法以 /proc/interrupts 为主:RISC-V 平台上 IPI 各类型占一行、每核一列计数。系统空载时计数近乎静止;跨核任务迁移、大规模内存释放等操作会立即使对应列增长。
六、实战:QEMU virt 双核 IPI 验证
Linux 侧观察 IPI 计数的完整流程:
bash
qemu-system-riscv64 -machine virt -smp 2 -m 1G \
-bios fw_dynamic.bin -kernel Image \
-append "console=ttyS0" -nographic
# 基线计数
grep IPI /proc/interrupts
# 制造跨核调度负载
taskset -c 1 sh -c 'while :; do :; done' &
sleep 2
# 再次观察,IPI 计数应显著增长
grep IPI /proc/interrupts
裸机侧以 M 模式双 Hart 为例,Hart 0 发送、Hart 1 接收:
c
/* Hart 0:通知 Hart 1 */
MSIP(1) = 1;
/* Hart 1:软件中断处理函数 */
void msoft_irq_handler(void)
{
MSIP(1) = 0; /* 写 0 清除挂起位 */
puts("hart1: IPI received\n");
}
需要特别注意:处理函数内必须回写 0 清除挂起位。这是 MSIP 与多数外设中断"读 claim 即自动清挂起"行为的本质差异,MSIP 只认显式写 0,漏写将导致目标核反复陷入软件中断。
七、常见坑位
- MSIP 漏清零:处理函数未回写 0,目标核在中断返回后立即再次陷入,表现为"一次 IPI 死循环"。清除动作必须在中断上下文内完成。
- Hart ID 与逻辑 CPU 号混用 :Linux 的逻辑 CPU 编号与硬件 Hart ID 是两个空间,二者经设备树拓扑映射关联;裸机代码与内核驱动中对齐二者时,应以
cpuid_to_hartid_map的映射结果为准。 - S 模式直写 CLINT 越权:MSIP 属 M 模式资源,S 模式直写将触发访问异常;S 模式的唯一合法路径是 SBI ecall。
- IPI 处理函数内执行重负载:IPI 在目标核的中断上下文中执行,长耗时回调会拖慢目标核并放大延迟;重活应转移到软中断或工作队列。
- 位图并发竞争:多核同时构造 Hart 位图发送 IPI 时,位图更新必须使用原子操作,否则目标集合可能丢失或重复;直接使用 SBI 批量接口可规避手工位图维护。
八、总结
三个结论:
- 一个硬件原语:IPI 的最底层始终是"向目标核置起软件中断挂起位",三条路径只是这一原语在不同特权级上的封装形式;
- 一次职责转移:SBI 路径以 trap 开销换取特权级隔离,AIA 将投递权下放给 S 模式后,延迟与隔离得以兼得;
- 一组观察手段 :
/proc/interrupts的 IPI 计数是验证多核中断路径的第一现场,驱动层面的任何结论都应以计数变化为最终判据。
核间中断打通后,多核系统的"通知---同步---失效"三类操作均有了硬件支撑。下篇预告:设备树中断绑定解析------中断号如何从设备树描述最终落到 irqchip 域中的虚拟中断号。