Intel 计划在Linux kernel中引入 User Interrupts,效率是eventfd的10倍

Posted 从善若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Intel 计划在Linux kernel中引入 User Interrupts,效率是eventfd的10倍相关的知识,希望对你有一定的参考价值。

未来Eventfd的替代品 User Interrupts

        近期,在Linux kernel邮件列表中看到了Intel计划在最新的硬件中支持 User Interrupts功能,并在linux kernel社区中征求意见。邮件中给出了测试的性能数据,如下图:

IPI : inter-processor interrupt (邮件中将User task间的中断称为 User IPI)

进程间通信类型相对时间(已归一到 User IPI)
User IPI1.0
Signal14.8
Eventfd9.7
Pipe16.3
Domain17.3

详细介绍什么是 User Interrupts

        用户中断(Uintr)是一种硬件技术,可以将中断直接传递到用户空间。

        今天,几乎所有跨越特权界限的交流都是通过内核。 这些包括信号、管道、远程过程调用和基于硬件中断的通知。 User Interrupts提供了一个基础,通过避免通过内核的转换来实现常见的操作,以获得更高的效率(低延迟和低CPU利用率)

        在User Interrupts硬件体系结构中,接收者总是被期望用户空间任务( user space task)。 但是,一个User Interrupt 可以由另一个用户空间任务、内核或外部源(如设备)发送


底层是如何工作的?

        User Interrupts是一种中断传递机制。中断首先被发送到某个内存位置,然后再被发送到具体的接收者(要求接收者的CPL=3)。

内核管理相关的数据结构

  • UPID : User Posted Interrupt Descriptor — 保持接收者的中断向量信息和通知状态(如正在进行的通知、被抑制通知);

  • UITT : User Interrupt Target Table — 存储UPID指针 和 用于发送端中断路由的向量信息 ;

每个任务的中断状态通过 MSRs 记录,并且在上下文切换期间由内核恢复。

User IPI

senduipi - 根据UITT 索引发送一个 user IPI 到目标task

当 User IPI 发送者执行’senduipi '时,硬件根据UITT和index确定中断发送目标,并将中断向量(63-0)推送到接收者的UPID中。

如果接收端正在运行(CPL=3),发送端cpu将发送一个物理IPI到接收机的cpu。 在接收端,此IPI被检测为用户中断。 调用接收者的用户中断处理程序,并将中断向量(63-0)压栈。


应用接口

        User Interrupts (Uintr)是一个可选择的特性(不像信号)。希望使用Uintr的应用程序需要使用与Uintr相关的系统调用来向内核注册自己。Uintr接收器总是一个用户空间任务。一个Uintr sender可以是另一个用户空间任务,内核或设备。

  • 一个接收器可以注册/注销一个中断处理程序使用Uintr接收器相关的系统调用:
    • uintr_register_handler(handler, flags)
    • uintr_unregister_handler(flags)
  • 系统调用还允许接收者注册一个vector 并创建一个用户中断文件描述符 — uintr_fd:
    • uintr_fd = uintr_create_fd(vector, flags)

当eventfd或signal 用于频繁的用户空间事件通知时,Uintr是有用的。uintr_fd的语义有点类似于eventfd()或pipe的写端。

  • 任何发送方都可以使用 uintr_fd 向接收方传递事件(例如,中断)。发送方可以使用uintr_fd 管理与接收者之间的连接:
    • uipi_index = uintr_register_sender(uintr_fd, flags)
  • 在初始设置之后,发送端可以使用 SENDUIPI 指令以及uipi_index 参数来生成 User IPI,而不需要任何内核干预:
    • SENDUIPI <uipi_index>

如果接收端正在运行(CPL=3),则直接交付用户中断,而不需要内核转换。如果接收端没有运行,那么当接收端获得上下文切换回时,中断就会被交付。如果接收者在内核中被阻塞,用户中断被传递到内核,内核然后解除阻塞,向接收者传递中断。

  • 如果发送方是内核或设备,则可以将 uintr_fd 传递给相关的内核实体,以允许它们设置连接,然后生成用于事件传递的 user interrupt。

具体的例子

#define _GNU_SOURCE
#include <syscall.h>
#include <stdio.h>
#include <unistd.h>
#include <x86gprintrin.h>
#include <pthread.h>
#include <stdlib.h>

unsigned long uintr_received;
unsigned int uintr_fd;

void __attribute__((interrupt))__attribute__((target("general-regs-only", "inline-all-stringops")))
uintr_handler(struct __uintr_frame *ui_frame,
	      unsigned long long vector)
{
	uintr_received = 1;
}

void receiver_setup_interrupt(void)
{
	int vector = 0;
	int ret;

	/* Register interrupt handler */
	if (uintr_register_handler(uintr_handler, 0)) {
		printf("[FAIL]\\tInterrupt handler register error\\n");
		exit(EXIT_FAILURE);
	}

	/* Create uintr_fd */
	ret = uintr_create_fd(vector, 0);
	if (ret < 0) {
		printf("[FAIL]\\tInterrupt vector registration error\\n");
		exit(EXIT_FAILURE);
	}

	uintr_fd = ret;
}

void *sender_thread(void *arg)
{
	long sleep_usec = (long)arg;
	int uipi_index;

	uipi_index = uintr_register_sender(uintr_fd, 0);
	if (uipi_index < 0) {
		printf("[FAIL]\\tSender register error\\n");
		return NULL;
	}

	/* Sleep before sending IPI to allow the receiver to block in the kernel */
	if (sleep_usec)
		usleep(sleep_usec);

	printf("\\tother thread: sending IPI\\n");
	_senduipi(uipi_index);

	uintr_unregister_sender(uintr_fd, 0);

	return NULL;
}

static inline void cpu_relax(void)
{
	asm volatile("rep; nop" ::: "memory");
}

void test_base_ipi(void)
{
	pthread_t pt;

	uintr_received = 0;
	if (pthread_create(&pt, NULL, &sender_thread, NULL)) {
		printf("[FAIL]\\tError creating sender thread\\n");
		return;
	}

	printf("[RUN]\\tSpin in userspace (waiting for interrupts)\\n");
	// Keep spinning until interrupt received
	while (!uintr_received)
		cpu_relax();

	printf("[OK]\\tUser interrupt received\\n");
}

void test_blocking_ipi(void)
{
	pthread_t pt;
	long sleep_usec;

	uintr_received = 0;
	sleep_usec = 1000;
	if (pthread_create(&pt, NULL, &sender_thread, (void *)sleep_usec)) {
		printf("[FAIL]\\tError creating sender thread\\n");
		return;
	}

	printf("[RUN]\\tBlock in the kernel (waiting for interrupts)\\n");
	uintr_wait(0);
	if (uintr_received)
		printf("[OK]\\tUser interrupt received\\n");
	else
		printf("[FAIL]\\tUser interrupt not received\\n");
}

int main(int argc, char *argv[])
{
	receiver_setup_interrupt();

	/* Enable interrupts */
	_stui();

	test_base_ipi();

	test_blocking_ipi();

	close(uintr_fd);
	uintr_unregister_handler(0);

	exit(EXIT_SUCCESS);
}

以上是关于Intel 计划在Linux kernel中引入 User Interrupts,效率是eventfd的10倍的主要内容,如果未能解决你的问题,请参考以下文章

Linux Kernel TCP/IP Stack — L1 Layer — 多队列网卡

Linux Kernel TCP/IP Stack — L1 Layer — 多队列网卡

linux-kernel 学习计划

Intel MKL(Math Kernel Library)

OpenCL设计优化(基于Intel FPGA SDK for OpenCL)

Linux的创建者Torvalds宣布推出了Linux Kernel 5.7 RC7