作者:罗宇哲,中国科学院软件研究所智能软件研究中心
上一期我们中我们介绍了ARM Linux内核中外设中断的一般流程,这一期中我们将介绍软件产生的中断的处理流程。
软件产生的中断(SGI)是ARM GICv3支持的一种中断类型,SGI通常用于处理器间的通行,通过写GIC的SGI寄存器能产生SGI。
ARM Linux内核定义了多个CPU间交互的接口,这些接口在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/include/linux/smp.h文件中可以找到:
在上一期中我们提到,ARM Linux内核将硬件中断号小于16的中断识别为SGI,并调用handle_IPI()函数处理。在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/arch/arm64/kernel/smp.c文件中我们可以找到handle_IPI()函数的代码:
/\*
\* Main handler for inter-processor interrupts
\*/
void handle_IPI(int ipinr, struct pt_regs \*regs)
{
unsigned int cpu = smp_processor_id();
struct pt_regs \*old_regs = set_irq_regs(regs);
bool irqs_enabled = interrupts_enabled(regs);
if ((unsigned)ipinr \< NR_IPI) {
trace_ipi_entry_rcuidle(ipi_types[ipinr]);
\__inc_irq_stat(cpu, ipi_irqs[ipinr]);
}
switch (ipinr) {
case IPI_RESCHEDULE:
scheduler_ipi();
break;
case IPI_CALL_FUNC:
irq_enter();
generic_smp_call_function_interrupt();
irq_exit();
break;
case IPI_CPU_STOP:
irq_enter();
ipi_cpu_stop(cpu);
irq_exit();
break;
case IPI_CPU_CRASH_STOP:
if (IS_ENABLED(CONFIG_KEXEC_CORE)) {
if (gic_supports_pseudo_nmis()) {
if (irqs_enabled)
nmi_enter();
} else
irq_enter();
ipi_cpu_crash_stop(cpu, regs);
unreachable();
}
break;
\#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
case IPI_TIMER:
irq_enter();
tick_receive_broadcast();
irq_exit();
break;
\#endif
\#ifdef CONFIG_IRQ_WORK
case IPI_IRQ_WORK:
irq_enter();
irq_work_run();
irq_exit();
break;
\#endif
\#ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
case IPI_WAKEUP:
WARN_ONCE(!acpi_parking_protocol_valid(cpu),
"CPU%u: Wake-up IPI outside the ACPI parking protocol\\n",
cpu);
break;
\#endif
case IPI_CPU_BACKTRACE:
if (gic_supports_pseudo_nmis()) {
if (irqs_enabled)
nmi_enter();
} else {
printk_nmi_enter();
irq_enter();
}
nmi_cpu_backtrace(regs);
if (gic_supports_pseudo_nmis()) {
if (irqs_enabled)
nmi_exit();
} else {
irq_exit();
printk_nmi_exit();
}
break;
default:
pr_crit("CPU%u: Unknown IPI message 0x%x\\n", cpu, ipinr);
break;
}
if ((unsigned)ipinr \< NR_IPI)
trace_ipi_exit_rcuidle(ipi_types[ipinr]);
set_irq_regs(old_regs);
}
从上面的函数可以看出,ARM Linux内核支持的SGI有如下几种类型1:
void scheduler_ipi(void)
{
……
irq_enter();
sched_ttwu_pending();
/\*
\* Check if someone kicked us for doing the nohz idle load balance.
\*/
if (unlikely(got_nohz_idle_kick())) {
this_rq()-\>idle_balance = 1;
raise_softirq_irqoff(SCHED_SOFTIRQ);
}
irq_exit();
}
scheduler_ipi()函数调用sched_ttwu_pending()函数唤醒pending的任务然后调用raise_softirq_irqoff()函数发起一个软中断,软中断将在后续的文章中介绍。
static void flush_smp_call_function_queue(bool warn_cpu_offline)
{
……
llist_for_each_entry_safe(csd, csd_next, entry, llist) {
smp_call_func_t func = csd-\>func;
void \*info = csd-\>info;
/\* Do we wait until \*after\* callback? \*/
if (csd-\>flags & CSD_FLAG_SYNCHRONOUS) {
func(info);
csd_unlock(csd);
} else {
csd_unlock(csd);
func(info);
}
}
……
}
/\*
\* ipi_cpu_stop - handle IPI from smp_send_stop()
\*/
static void ipi_cpu_stop(unsigned int cpu)
{
set_cpu_online(cpu, false);
local_daif_mask();
sdei_mask_local_cpu();
while (1)
cpu_relax();
}
该函数将CPU标记为offline状态,将DAIF标志位置一,然后循环调用cpu_relax()。cpu_relax()的代码在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/arch/arm64/include/asm/processor.h
文件中可以找到:
该函数包含一个内存屏障和一个yield指令,可以降低处理器功耗并且将系统资源让渡给其它线程2。
static void irq_work_run_list(struct llist_head \*list)
{
……
llnode = llist_del_all(list);
llist_for_each_entry_safe(work, tmp, llnode, llnode) {
……
work-\>func(work);
……
}
}
本期我们介绍了ARM Linux内核中SGI 的处理流程,下一期我们将介绍ARM Linux内核中与中断相关的常用函数。
《Linux内核深度解析》,余华兵著,2019 ↩︎
https://blog.csdn.net/wukongmingjing/article/details/82388970 ↩︎