第三十五期-ARM Linux内核的中断(5)

作者:罗宇哲,中国科学院软件研究所智能软件研究中心

上一期我们中我们介绍了ARM Linux内核中外设中断的一般流程,这一期中我们将介绍软件产生的中断的处理流程。

一、ARM Linux内核中软件产生的中断的处理流程

软件产生的中断(SGI)是ARM GICv3支持的一种中断类型,SGI通常用于处理器间的通行,通过写GIC的SGI寄存器能产生SGI。

ARM Linux内核定义了多个CPU间交互的接口,这些接口在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/include/linux/smp.h文件中可以找到:

  • void smp_send_stop(void):停止除了当前处理器外的所有处理器;
  • void smp_send_reschedule(int cpu):向其它处理器发送一个重新调度的事件,以重新调度进程;
  • void smp_prepare_cpus(unsigned int max_cpus):让机器做好启动其它CPU的准备;
  • int smp_call_function(smp_call_func_t func, void *info, int wait):在其它所有处理器上运行一个函数。

在上一期中我们提到,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

  • IPI_RESCHEDULE:函数smp_send_reschedule()生成的中断,该中断重新调度进程,调用的函数scheduler_IPI()在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/kernel/sched/core.c文件中可以找到:
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()函数发起一个软中断,软中断将在后续的文章中介绍。

  • IPI_CALL_FUNC:函数smp_call_function()生成的中断,通过调用函数generic_smp_call_function_interrupt()最终调用函数flush_smp_call_function_queue(),该函数调用所有在队列中pending的回调函数。flush_smp_call_function_queue()函数的源码可以在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/kernel/smp.c文件中可以找到:
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:函数smp_send_stop()生成的中断,调用函数ipi_cpu_stop()。函数ipi_cpu_stop()的代码可以在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/arch/arm64/kernel/smp.c文件中找到:
/\*

\* 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

文件中可以找到:
第三十五期-ARM Linux内核的中断(5)_第1张图片
该函数包含一个内存屏障和一个yield指令,可以降低处理器功耗并且将系统资源让渡给其它线程2

  • IPI_CPU_CRASH_STOP:该中断使处理器停止;
  • IPI_TIMER:该中断广播时钟事件;
  • IPI_IRQ_WORK:在中断上下文中执行回调函数,irq_work_run()函数的源码可以在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/kernel/irq_work.c文件中可以找到:
    第三十五期-ARM Linux内核的中断(5)_第2张图片
    它通过irq_work_run_list()函数调用链表中的回调函数,irq_work_run_list()的源码在irq_work.c文件中也可以找到:
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);

……

}

}
  • IPI_WAKEUP:唤醒处理器。
  • IPI_CPU_BACKTRACE:打印函数调用栈。

二、结语

本期我们介绍了ARM Linux内核中SGI 的处理流程,下一期我们将介绍ARM Linux内核中与中断相关的常用函数。
第三十五期-ARM Linux内核的中断(5)_第3张图片


  1. 《Linux内核深度解析》,余华兵著,2019 ↩︎

  2. https://blog.csdn.net/wukongmingjing/article/details/82388970 ↩︎

你可能感兴趣的:(内核,linux,java,android,操作系统)