概念

IPI(Inter-Processor Interrupt,处理器间中断)。

处理器间中断允许一个 CPU 向其它 CPU 发送中断信号,处理器间中断不是通过 IRQ 线传输的,而是作为信号直接放在连接所有 CPU 本地 APIC 的总线上。

IPI 需要 GIC 支持吗

IPI(Inter-Processor Interrupt,处理器间中断)是用于处理器内部通讯的中断。在ARM架构中,IPI通常需要由GIC(Generic Interrupt Controller,通用中断控制器)来管理和实现。GIC可以在处理器间转发IPI信号,以便在多个处理器之间进行协作和通讯。通过GIC,处理器可以指定IPI优先级和处理方式,从而灵地控制IPI的处理此,GIC是对IPI信号进行处理和转发的必要组件,也是支持多处理器通讯和协作的关键组件。

IPI 需要使用 IRQ 线吗?

IPI 通常不 线进行传输,因为 IPI 是一种处理器间的中断机制,主要用于同一个处理器中的多个核之间的通讯。在 ARM 架构中,IPI 可以通过 LPI。LPI 是一种与 IRQ 相关的本地中断机制,但与 IRQ 不同,LPI 不需要通过中断控制器进行路由,可以直接在处理器内部传递和处理。在 ARMv8 架构中,LPI 被广泛用于处理器间通讯和协作,同时可以通过 GIC 自动映射到硬件中断,提高了处理器的并行度和性能。

LPI 是 GIC 内部设施吗?还是 SOC 内部的设施?

LPI(Local Processor Interrupt,本地处理器中断)是 ARMv8 架构中的一种中断机制,用于处理器内部的中断处理。 LPI 是由 GIC(Generic Interrupt Controller,通用中断控制器)内部提供的中断机制,主要用于处理器内部的通讯和协作。因此,LPI 是 GIC 内部设施。但是,LPI 这种中断机制也是 SOC(System on a Chip,片上系统)内部的设施,因为它被嵌入在 SOC 架构中,用于处理 SOC 内部的中断需求。

pic 和 gic 区别

PIC(Programmable Interrupt Controller,可编程中断控制器)和GIC(Generic Interrupt Controller,通用中断控制器)都是用于管理中断的硬件组件。它们的主要区别在于: 1. 架构不同:PIC是x86架构中的中断控制器,而GIC是ARM架构中的中断控制器。 2. 可编程性:PIC不支持动态配置和优先级控制,而GIC支持动态配置和优先级控制。这意味着GIC可以灵活地适应不同的硬件平台和应用场景。 3. 中断处理方式:PIC是经典的中断处理方式,即通过向CPU发送中断请求来引发中断处理程序。而GIC支持多种中断处理方式,包括虚拟中断和消息传递中断。 总之,PIC和GIC都是用于管理中断的硬件组件,但在架构、可编程性和中断处理方式等面有所不同。

一个实例说明

参考:IPI中断机制

XLR 732 (x86架构)多核多线程处理器的中断由 PIC(Programmable Interrupt Controller)统一控制。PIC 允许一个硬件线程中断其他的硬件线程,这种方式被称为核间中断(Inter-Processor Interrupts,IPI)。

PIC 拥有一个宽度为 32 位的核间中断寄存器 IPI Base,该寄存器包含目的线程的编号、中断向量及中断类型(是否中断多个硬件线程)等内容。核间中断可以通过向这个寄存器写入需要的值来产生

若硬件线程 A 想要发送一个核间中断给硬件线程 B,它只需要向寄存器 IPI Base 中写入 B 的 Thread ID、中断向量、中断类型等值就可以了PIC 会通知 B 所在的核挂起它当前的执行序列,并根据中断向量跳转到中断服务例程 ISR 的入口

使用 IPI 进行核间通信的关键在于要利用中断服务例程 ISR 去读取一个事先约好的共享内存区域。发起方首先将消息写到一块共享内存中,然后发起核间中断。被中断的硬件线程在中断服务例程中读取该内存,以获得发起方通知的消息。为防止多核间的竞争导致消息被改写,使用这种方式必须利用锁机制来确保消息的完整性。

例程1

arch/arm/kernel/smp.c

void smp_send_reschedule(int cpu)
{
	smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE);
}
EXPORT_SYMBOL(smp_send_reschedule); // 导出符号,不然 ko 无法编译通过

include/linux/sched.h

static __always_inline void scheduler_ipi(void)
{
printk("CPU%d: scheduler_ipi()\n", smp_processor_id()); // 添加打印
	/*
	 * Fold TIF_NEED_RESCHED into the preempt_count; anybody setting
	 * TIF_NEED_RESCHED remotely (for the first time) will also send
	 * this IPI.
	 */
	preempt_fold_need_resched();
}

重新编译内核

liyongjun@Box:~/project/board/buildroot$ rm OrangePiPC/build/linux-custom/.stamp_built
liyongjun@Box:~/project/board/buildroot$ make O=OrangePiPC/ linux -j4 V=s
liyongjun@Box:~/project/board/buildroot$ cp OrangePiPC/images/zImage ~/tftp/

测试 ko

hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <asm/io.h>

static int hello_init(void)
{
	printk("hello_init\n");
	smp_send_reschedule(3);

	return 0;
}

static void hello_exit(void)
{
	printk("hello_exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");

Makefile

obj-m = hello.o

KDIR = /home/liyongjun/project/board/buildroot/OrangePiPC/build/linux-custom
CROSS_COMPILE = /home/liyongjun/project/board/buildroot/OrangePiPC/host/bin/arm-linux-

EXTRA_CFLAGS = -g

all:
	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean

硬件:OrangePi PC

更换内核

# cd /boot/
# tftp -gr zImage 192.168.31.223
# reboot

下载 ko

# tftp -gr hello.ko 192.168.31.223

设置 printk 打印等级

# echo 7 > /proc/sys/kernel/printk

运行

# insmod hello.ko
[  399.773120] CPU1: scheduler_ipi()
[  399.781822] hello_init
[  399.787149] CPU3: scheduler_ipi() // 生效了

CPU1 执行 smp_send_reschedule(3); 让 CPU3 执行调度。

例程2

这个例程才是比较通用

hello.c

#include <linux/module.h>
#include <linux/smp.h>

static void print_str(void *str)
{
	printk("CPU%d do task\n", smp_processor_id());
	printk("%s\n", (char *)str);
}

static int hello_init(void)
{
	char str[] = "hello";

	printk("hello_init\n");
	smp_call_function_single(2, print_str, str, 1); // 让 CPU2 执行 print_str()

	return 0;
}

static void hello_exit(void)
{
	printk("hello_exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");

Makefile

obj-m = hello.o

KDIR = /home/liyongjun/project/board/buildroot/OrangePiPC/build/linux-custom
CROSS_COMPILE = /home/liyongjun/project/board/buildroot/OrangePiPC/host/bin/arm-linux-

EXTRA_CFLAGS = -g

all:
	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean

install:
	cp hello.ko ~/tftp

OrangePi PC 运行

# tftp -gr hello.ko 192.168.31.223
#
# insmod hello.ko
[167594.203052] CPU0: scheduler_ipi()
[167594.211962] hello_init
[167594.214459] CPU2 do task
[167594.217086] hello
#
# rmmod hello.ko
[167597.888388] CPU0: scheduler_ipi()
[167597.897441] hello_exit

参考

处理器间中断处理(IPI)

Linux内核SMP机间中断(二)

全面解析Linux 内核 3.10.x - IPI核间中断的应用

IPI中断机制 PIC 是 x86 架构中的中断控制器,而 GIC 是 ARM 架构中的中断控制器。

流水灯

linux内核:一文读懂IPI核间中断(arm64架构)

linux中断子系统详解