1. IPI中断介绍
IPI中断,即处理器核间中断(Inter-Processor Interrupt),其实就是ARM GIC架构里定义的SGI中断(Software Generated Interrupt)。在GICv3架构中,共有16个SGI中断(不包括extension的),中断号是从0到15,如下图。
核间中断就是一个CPU核(PE)向系统中的目标CPU核(target PE)发送中断信号,以使目标CPU执行特定的操作。IPI中断的基本流程为: PE触发 -> GICR -> GICD -> target GICR -> target CPU interface -> target PE。
IPI中断的触发:PE通过写系统寄存器ICC_SGI0R_EL1、ICC_SGI1R_EL1或ICC_ASGI1R_EL1来触发。
IPI中断有两种routing模式(通过ICC_SGI0R_EL1.IRM/ ICC_SGI1R_EL1.IRM设置):
IRM bit为1,SGI会广播给系统中所有PE(除了产生SGI的PE)。
IRM bit为0,SGI会发给指定的一些PE,通过Aff3.Aff2.Aff1.指定。(可以包括产生SGI的PE)。
ICC_SGI0R_EL1/ICC_SGI1R_EL1寄存器
2. linux里的IPI中断类型
在Linux kernel代码中,默认定义了8种IPI中断(SGI0 - SGI7),具体如下(linux/arch/arm64/kernel/smp.c):
enum ipi_msg_type {
IPI_RESCHEDULE,
IPI_CALL_FUNC,
IPI_CPU_STOP,
IPI_CPU_CRASH_STOP,
IPI_TIMER,
IPI_IRQ_WORK,
IPI_WAKEUP,
NR_IPI};
IPI_RESCHEDULE
0号中断,重新调度进程scheduler_ipi()。
一般是先为进程设置TIF_NEED_RESCHED标志(表示需要调度该进程),如果该进程没有在当前CPU上,会通过smp_send_reschedule接口触发IPI_RESCHEDULE中断给目标CPU,目标CPU最终进入hadle_IPI的scheduler_ipi中。
IPI_CALL_FUNC
1号中断,调用generic_smp_call_function_interrupt(),远程cpu执行回调函数。
如果想在某个cpu(不是本cpu)上调用一个函数时,会触发IPI_CALL_FUNC中断,常用smp_call_function接口触发target cpu的IPI_CALL_FUNC软中断。
IPI_CPU_STOP
2号中断,调用local_cpu_stop()函数使当前CPU停止工作,进入wfi/wfe的低功耗状态。
当某个cpu想让其他cpu停下来时会发送此IPI,主要接口是machine_halt, machine_power_off, machine_restart。这些接口都会调用smp_send_stop来触发IPI_CPU_STOP中断,target cpu收到中断后会调用local_cpu_stop()函数,如下:
static void local_cpu_stop(void)
{
set_cpu_online(smp_processor_id(), false);/*把当前cpu offline*/
local_daif_mask(); /*设置pstate的DAIF状态位为1,关闭本cpu的系统调试(D),系统错误SError(A),IRQ中断(I),FIQ中断(F)*/
sdei_mask_local_cpu();
cpu_park_loop(); /*通过wfe和wfi指令,让当前cpu进入低功耗standby状态*/
}
IPI_CPU_CRASH_STOP
3号中断,调用ipi_cpu_crash_stop(),使处理器停止。
在系统crash时,发生crash的cpu给其他cpu发送该中断。在使能了KEXEC的系统中,做出以下响应,保存寄存器信息并传递给第二内核。KEXEC常用于系统crash时在不重启的情况下快速进入第二内核。第二内核的目的是把当前ddr内存镜像保存下来,方便之后通过crash tool分析系统crash问题。
IPI_TIMER :
4号中断,调用tick_receive_broadcast(), 广播时钟事件。
当某个cpu调用tick_broadcast(const struct cpumask *mask)时,即调用IPI_TIMER中断给相应cpu(通过mask指定cpu)发送timer的广播中断,target cpu执行tick_receive_broadcast()函数进行响应。
IPI_IRQ_WORK :
5号中断,调用irq_work_run(),在中断上下文中执行回调函数。
IPI_WAKEUP :
6号中断,调用acpi_parking_protocol_valid(cpu), 唤醒CPU。当CPU核收到该IPI中断,即从parked的状态(wfi/wfe的低功耗状态)唤醒过来。
NR_IPI :
7号中断,没有使用。
3. IPI中断源码
负责处理IPI中断的函数主要是handle_IPI,源代码如下:
<arch/arm64/kernel/smp.c>
void handle_IPI(int ipinr, struct pt_regs *regs)
{
unsigned int cpu = smp_processor_id(); /*获得当前cpu id*/
struct pt_regs *old_regs = set_irq_regs(regs); /*pt_regs结构体包含当前的寄存器信息*/
if ((unsigned)ipinr < NR_IPI) { /*当前有效的IPI中断为7个*/
trace_ipi_entry_rcuidle(ipi_types[ipinr]); /* ftrace记录进入ipi中断,用于debug */
__inc_irq_stat(cpu, ipi_irqs[ipinr]); /* 统计各cpu不同类型的ipi中断数量*/
}
switch (ipinr) {
case IPI_RESCHEDULE:
scheduler_ipi(); /* 触发重调度 */
break;
case IPI_CALL_FUNC:
irq_enter();
generic_smp_call_function_interrupt(); /*执行本cpu所有function回调 */
irq_exit();
break;
case IPI_CPU_STOP:
irq_enter();
local_cpu_stop(); /*将本cpu停下来,进入低功耗状态*/
irq_exit();
break;
case IPI_CPU_CRASH_STOP:
if (IS_ENABLED(CONFIG_KEXEC_CORE)) { /*如果配置了KEXEC,在系统panic时会进入第二内核*/
irq_enter();
ipi_cpu_crash_stop(cpu, regs);
unreachable();
}
break;
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
case IPI_TIMER:
irq_enter();
tick_receive_broadcast(); /* 接收timer广播,执行timer的中断回调 */
irq_exit();
break;
#endif
#ifdef CONFIG_IRQ_WORK
case IPI_IRQ_WORK:
irq_enter();
irq_work_run(); /*本cpu执行irq_work */
irq_exit();
break;
#endif
#ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
case IPI_WAKEUP: /* 从低功耗状态中唤醒本cpu */
WARN_ONCE(!acpi_parking_protocol_valid(cpu),
"CPU%u: Wake-up IPI outside the ACPI parking protocoln",
cpu);
break;
#endif
default:
pr_crit("CPU%u: Unknown IPI message 0x%xn", cpu, ipinr);
break;
}
if ((unsigned)ipinr < NR_IPI)
trace_ipi_exit_rcuidle(ipi_types[ipinr]);
set_irq_regs(old_regs);
}