未来Eventfd的替代品 User Interrupts
????????近期,在Linux kernel邮件列表中看到了Intel计划在最新的硬件中支持 User Interrupts功能,并在linux kernel社区中征求意见。邮件中给出了测试的性能数据,如下图:
IPI : inter-processor interrupt (邮件中将User task间的中断称为 User IPI)
进程间通信类型 | 相对时间(已归一到 User IPI) |
---|
User IPI | 1.0 | Signal | 14.8 | Eventfd | 9.7 | Pipe | 16.3 | Domain | 17.3 |
详细介绍什么是 User Interrupts
????????用户中断(Uintr)是一种硬件技术,可以将中断直接传递到用户空间。
????????今天,几乎所有跨越特权界限的交流都是通过内核。 这些包括信号、管道、远程过程调用和基于硬件中断的通知。 User Interrupts提供了一个基础,通过避免通过内核的转换来实现常见的操作,以获得更高的效率(低延迟和低CPU利用率)。
????????在User Interrupts硬件体系结构中,接收者总是被期望用户空间任务( user space task)。 但是,一个User Interrupt 可以由另一个用户空间任务、内核或外部源(如设备)发送。
底层是如何工作的?
????????User Interrupts是一种中断传递机制。中断首先被发送到某个内存位置,然后再被发送到具体的接收者(要求接收者的CPL=3)。
内核管理相关的数据结构
每个任务的中断状态通过 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,而不需要任何内核干预:
如果接收端正在运行(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;
if (uintr_register_handler(uintr_handler, 0)) {
printf("[FAIL]\tInterrupt handler register error\n");
exit(EXIT_FAILURE);
}
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;
}
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");
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();
_stui();
test_base_ipi();
test_blocking_ipi();
close(uintr_fd);
uintr_unregister_handler(0);
exit(EXIT_SUCCESS);
}
|