赞
踩
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
本文以 Linux 4.14 内核
,ARM 架构
,GIC 中断芯片
为背景,简要分析 SMP
架构下 CPU 核间通信
。
Linux 系统下,SMP
架构 CPU 核间通过预定义的 IPI(Inter-Processor Interrupts)
中断来进行通信:
enum ipi_msg_type { IPI_WAKEUP, /* 定时器广播消息 */ IPI_TIMER, /* * 远程 CPU 执行任务调度。 * 当 被唤醒进程 不是运行于本地 CPU 上时,就需要给 被唤醒进程 所在的远程 * CPU 发送 IPI 中断,让远程 CPU 发起调度。 * 在接收到 IPI 时无论远程 CPU 是否处于空闲态,被唤醒进程 都会在 IPI 中断 * 进入到内核态时得到执行。 */ IPI_RESCHEDULE, /* 在指定 CPU 集合上运行函数 */ IPI_CALL_FUNC, /* * 远程 CPU 停止运行。 * 先将指定的远程 CPU 从在线 CPU 位图中删除,然后将 * D,A,I,F 中断屏蔽,最后处理器进入低功耗循环。 */ IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, IPI_CPU_BACKTRACE, /* * SGI8-15 can be reserved by secure firmware, and thus may * not be usable by the kernel. Please keep the above limited * to at most 8 entries. */ };
上面的 IPI_*
消息是 Linux
为 SMP
架构下 CPU 核间通信
定义的、架构无关的消息类型,Linux
负责定义这些消息的语义;而这些消息语义的具体实现,依赖于具体的硬件架构类型。本文以 ARM
架构为例,举例说明几个 IPI_*
消息的实现。
ARM
架构下通过 GIC
芯片生成 SGI(Software-generated interrupt)
中断,来实现 IPI_*
消息的语义,其过程为:通过 GIC
生成一个 SGI
中断,发送给目标 CPU 集合,然后目标 CPU 处理 SGI
中断。
如 BOOT CPU
发送定时器广播事件
,唤醒系统中的其它非 BOOT CPU
。先生成一个编码了 IPI_TIMER 信息的 SGI 中断,发送给目标 CPU
:
/* arch/arm/kernel/smp.c */
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
void tick_broadcast(const struct cpumask *mask)
{
smp_cross_call(mask, IPI_TIMER);
}
#endif
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
{
trace_ipi_raise_rcuidle(target, ipi_types[ipinr]);
__smp_cross_call(target, ipinr); /* gic_raise_softirq(), ... */
}
#ifdef CONFIG_SMP
static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
{
...
/*
* GIC 生成一个 SGI 中断:
* map << 16: 接收 SGI 中断的目标 CPU 掩码
* @irq : 生成的 SGI 中断编号(如 IPI_TIMER)
*/
writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
...
}
目标 CPU 处理编码了 IPI_TIMER
信息的 SGI
中断:
gic_handle_irq()
handle_IPI(irqnr, regs);
/* arch/arm/kernel/smp.c */ void handle_IPI(int ipinr, struct pt_regs *regs) { ... if ((unsigned)ipinr < NR_IPI) { ... /* 统计 IPI 中断次数,可从 /proc/interrupts 观察到 */ __inc_irq_stat(cpu, ipi_irqs[ipinr]); } switch (ipinr) { ... #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST case IPI_TIMER: irq_enter(); tick_receive_broadcast(); irq_exit(); break; #endif ... } ... }
IPI_RESCHEDULE
用于将进程调度到非当前 CPU
运行:
void smp_send_reschedule(int cpu)
{
smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE); /* 流程同 IPI_TIMER */
}
看一下处理 IPI_RESCHEDULE
中断的处理流程:
gic_handle_irq() handle_IPI(irqnr, regs) switch (ipinr) { ... case IPI_RESCHEDULE: scheduler_ipi(); break; ... } void scheduler_ipi(void) { ... irq_enter(); sched_ttwu_pending(); ... irq_exit(); }
IPI_CALL_FUNC
用来在指定 CPU 上运行函数,如 smp_call_function_single()
在指定的 CPU 核上运行回调函数:
/* include/linux/smp.h */
int smp_call_function_single(int cpuid, smp_call_func_t func, void *info,
int wait);
/* kernel/smp.c */ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, int wait) { call_single_data_t *csd; call_single_data_t csd_stack = { /* CSD_FLAG_SYNCHRONOUS 标志,指示 等待 smp call 回调执行完成再返回 */ .flags = CSD_FLAG_LOCK | CSD_FLAG_SYNCHRONOUS, }; int this_cpu; int err; /* * prevent preemption and reschedule on another processor, * as well as CPU removal */ this_cpu = get_cpu(); ... csd = &csd_stack; if (!wait) { csd = this_cpu_ptr(&csd_data); csd_lock(csd); } err = generic_exec_single(cpu, csd, func, info); if (wait) csd_lock_wait(csd); /* 等待 回调 执行完成 */ put_cpu(); return err; } static int generic_exec_single(int cpu, call_single_data_t *csd, smp_call_func_t func, void *info) { if (cpu == smp_processor_id()) { /* 在当前 CPU 执行,不用发起 IPI */ /* * We can unlock early even for the synchronous on-stack case, * since we're doing this from the same CPU.. */ csd_unlock(csd); local_irq_save(flags); func(info); /* remote_function(), ... */ local_irq_restore(flags); return 0; } /* 在非当前 CPU 执行,发起 IPI */ ... csd->func = func; csd->info = info; /* * 通过 IPI 中断,发起远程 CPU 函数调用。 * 回调 @csd 添加到 @cpu 的 回调列表 @call_single_queue 。 */ if (llist_add(&csd->llist, &per_cpu(call_single_queue, cpu))) arch_send_call_function_single_ipi(cpu); return 0; }
void arch_send_call_function_single_ipi(int cpu)
{
smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC);
}
处理 IPI_CALL_FUNC
中断,在目标 CPU 上调用回调:
gic_handle_irq()
handle_IPI(irqnr, regs)
switch (ipinr) {
...
case IPI_CALL_FUNC:
irq_enter();
generic_smp_call_function_interrupt();
irq_exit();
break;
...
}
#define generic_smp_call_function_interrupt \
generic_smp_call_function_single_interrupt
void generic_smp_call_function_single_interrupt(void) { flush_smp_call_function_queue(true); } static void flush_smp_call_function_queue(bool warn_cpu_offline) { struct llist_head *head; struct llist_node *entry; call_single_data_t *csd, *csd_next; ... head = this_cpu_ptr(&call_single_queue); entry = llist_del_all(head); entry = llist_reverse_order(entry); ... /* 调用当前 CPU 回调列表 @call_single_queue 中的所有回调函数 */ 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) { /* smp call 等到 func 运行完成后返回,所以先不释放锁 */ func(info); csd_unlock(csd); } else { /* smp call 不等 func 运行完成就返回,所以先释放锁 */ csd_unlock(csd); func(info); } } ... }
感兴趣的读者可自行阅读源码分析。
感兴趣的读者可自行阅读源码分析。
# cat /proc/interrupts
[......]
IPI0: 39038745 339833 481351 716260 Rescheduling interrupts
IPI1: 495285 1329854 3247 1819 Function call interrupts
IPI2: 0 0 0 0 CPU stop interrupts
IPI3: 0 0 0 0 CPU stop (for crash dump) interrupts
IPI4: 0 0 0 0 Timer broadcast interrupts
IPI5: 1 0 0 0 IRQ work interrupts
IPI6: 0 0 0 0 CPU wake-up interrupts
[......]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。