当前位置:   article > 正文

Linux:SMP 架构下 CPU 核间通信

Linux:SMP 架构下 CPU 核间通信

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文以 Linux 4.14 内核ARM 架构GIC 中断芯片为背景,简要分析 SMP 架构CPU 核间通信

3. 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.
	 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上面的 IPI_* 消息是 LinuxSMP 架构下 CPU 核间通信定义的、架构无关的消息类型,Linux 负责定义这些消息的语义;而这些消息语义的具体实现,依赖于具体的硬件架构类型。本文以 ARM 架构为例,举例说明几个 IPI_* 消息的实现。

3.1 ARM 架构下的 IPI_* 消息实现

ARM 架构下通过 GIC 芯片生成 SGI(Software-generated interrupt) 中断,来实现 IPI_* 消息的语义,其过程为:通过 GIC 生成一个 SGI 中断,发送给目标 CPU 集合,然后目标 CPU 处理 SGI 中断。

3.1.1 IPI_TIMER

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(), ... */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
#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);
	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

目标 CPU 处理编码了 IPI_TIMER 信息的 SGI 中断:

gic_handle_irq()
	handle_IPI(irqnr, regs);
  • 1
/* 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
	...
	}

	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.1.2 IPI_RESCHEDULE

IPI_RESCHEDULE 用于将进程调度到非当前 CPU 运行:

void smp_send_reschedule(int cpu)
{
	smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE); /* 流程同 IPI_TIMER */
}	
  • 1
  • 2
  • 3

看一下处理 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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.1.3 IPI_CALL_FUNC

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);
  • 1
  • 2
  • 3
/* 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
void arch_send_call_function_single_ipi(int cpu)
{
	smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC);
}
  • 1
  • 2
  • 3

处理 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;
		...
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
#define generic_smp_call_function_interrupt \
	generic_smp_call_function_single_interrupt
  • 1
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);
		}
	}

	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

3.1.4 其它 IPI 消息

感兴趣的读者可自行阅读源码分析。

3.2 其它架构 IPI 实现

感兴趣的读者可自行阅读源码分析。

4. 观察 IPI 中断发生次数

# 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
[......]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号