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_BROADCASTcase IPI_TIMER:irq_enter();tick_receive_broadcast(); /* 接收timer广播,执行timer的中断回调 */irq_exit();break;#endif#ifdef CONFIG_IRQ_WORKcase IPI_IRQ_WORK:irq_enter();irq_work_run(); /*本cpu执行irq_work */irq_exit();break;#endif#ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOLcase IPI_WAKEUP: /* 从低功耗状态中唤醒本cpu */WARN_ONCE(!acpi_parking_protocol_valid(cpu),"CPU%u: Wake-up IPI outside the ACPI parking protocoln",cpu);break;#endifdefault: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);}
4773