uintr 用户中断流程梳理
Intel 用户中断流程梳理
资料来源:
- https://github.com/OS-F-4/uintr-linux-kernel/commits/rfc-v1
- https://www.intel.com/content/dam/develop/external/us/en/documents/architecture-instruction-set-extensions-programming-reference.pdf
- https://github.com/OS-F-4/usr-intr/tree/main/ppt
- https://www.0xaa55.com/thread-27327-1-1.html
更新
1
2023.08.07 初始
导语
基于 https://github.com/OS-F-4/uintr-linux-kernel/commits/rfc-v1 版本,梳理 用户中断从发送到接收的流程, 包含硬件执行流程 (这里对应 qemu).
- qemu 的实现 https://github.com/OS-F-4/qemu-uintr
uintr_receiver_wait 流程梳理,已有问题等.
发送
由 sample.c (位于 kernel 代码的 tools/uintr/sample
) 开始
1 | xxx |
发送方注册后,发送的起点是 _senduipi(uipi_index);
发送
send 执行 _senduipi(uipi_index);
定义在 gcc/x86_64-linux-gnu/11/include/uintrintrin.h
1 | _senduipi (unsigned long long __R) |
具体是 Enable gcc support for UINTR 这个 commit 给 gcc 增加的 uintr 支持.
1 | BDESC (OPTION_MASK_ISA_64BIT, OPTION_MASK_ISA2_UINTR, CODE_FOR_senduipi, "__builtin_ia32_senduipi", IX86_BUILTIN_SENDUIPI, UNKNOWN, (int) VOID_FTYPE_UINT64) |
_senduipi
指令在用户态完成; 硬件处理流程定义在 [[architecture-instruction-set-extensions-programming-reference.pdf#page=75]]
1 |
|
发送方发送的条件
- 用户中断启用 UIF
- UPID_ON 和 UPID_SN 都为 0
- 发送用户中断后,置位 UPID_ON
发送方并没有对发送的 tempUPID.NV
中断向量进行比较或操作.
对应到 qemu 的 target/i386/tcg/misc_helper.c 的 void helper_senduipi(CPUX86State *env ,int reg_index)
- 疑似有一个 bug
upid.nc.status&0x03
比较对象应该是 0x03 而不是 0x11
1 | void helper_senduipi(CPUX86State *env ,int reg_index){ |
接收
[[architecture-instruction-set-extensions-programming-reference.pdf#page=167|11.5.1 User-Interrupt Notification Identification]] 收到中断通知,开始判断
- The local APIC is acknowledged; this provides the processor core with an interrupt vector, V.
- 从 APIC 中读取 V
- If V = UINV, the logical processor continues to the next step. Otherwise, an interrupt with vector V is delivered normally through the IDT; the remainder of this algorithm does not apply and user-interrupt notification processing does not occur.
- V == UINV? 是就是 uintr 了执行第 3 步.否则直接跳转到 IDT 处理,普通中断处理. ^dfc1d3
- linux kernel 中 UINV 是 MSR_IA32_UINTR_MISC 的 39:32; 内核源码中每次都被赋值为 UINTR_NOTIFICATION_VECTOR(0xec) << 32
- The processor writes zero to the EOI register in the local APIC; this dismisses the interrupt with vector V = UINV from the local APIC.
- 写 EOI 寄存器,清除 V = UINV 的中断
[[architecture-instruction-set-extensions-programming-reference.pdf#page=168|11.5.2 User-Interrupt Notification Identification]] 从 IA32_UINTR_PD 读取 upid,开始处理 uintr.
- The logical processor clears the outstanding-notification bit (bit 0) in the UPID. This is done atomically so as to leave the remainder of the descriptor unmodified (e.g., with a locked AND operation).
- 清理 UPID 的 ON 位 (bit 0),对应 uintr_upid->nc->status 首位,并读取 PIR 到临时寄存器,并将 PIR 清零
- The logical processor reads PIR (bits 127:64 of the UPID) into a temporary register and writes all zeros to PIR. This is done atomically so as to ensure that each bit cleared is set in the temporary register (e.g., with a locked XCHG operation).
- If any bit is set in the temporary register, the logical processor sets in UIRR each bit corresponding to a bit set in the temporary register (e.g., with an OR operation) and recognizes a pending user interrupt (if it has not already done so).
- 2-3 步其实是将 UPID.PIR(uintr_upid->puir) 所有为 1 的位写入 UIRR,之后清空 UPID.PIR(uintr_upid->puir). ^5f2176
步骤 12 会在逻辑处理器上不间断执行,再执行第 3 步,最终跳跃到 11.4.2 真正的跳转处理中.
[[architecture-instruction-set-extensions-programming-reference.pdf#page=165|11.4.2 User-Interrupt Delivery]] 最终跳转到 UIHANDLER
1 | IF UIHANDLER is not canonical in current paging mode # UIHANDLER(handler 函数地址) 是不是合法 |
值得注意的几点:
- 接收时 会比较发送方发送的中断向量 与 UINV (MSR_IA32_UINTR_MISC 的 39:32).相同则认为其是用户中断, 否则就走普通中断处理流程.
- 接收时要求接收方进程处于前台 (用户态)
- 最终跳转到 接收方注册的 handler 函数执行用户中断.
qemu 中上面整个过程对应代码在 target/i386/tcg/seg_helper.c
:: static void do_interrupt64(CPUX86State *env, int intno, int is_int,int error_code, target_ulong next_eip, int is_hw)
1 | bool send = false; |
uintr_receiver_wait 流程梳理
rfc_v1 中添加了 uintr_receiver_wait
, 使得接收方可阻塞挂起等待用户中断.
为什么 uintr_receiver_wait 以后能够再唤醒线程 ?
uintr_receiver_wait
- upid->nc.nv = UINTR_KERNEL_VECTOR;
- 将发送方 upid 加入一个全局的 upid 等待 list (upid 中包含了进程的 trask_struct)
发送方发送的中断向量 就是 UINTR_KERNEL_VECTOR (0xeb) 而不是 用户中断对应的 UINTR_NOTIFICATION_VECTOR (0xec).
在处理 V == UINV? 时候就走到了 IDT 不再是用户中断,而是会 触发内核中断 -> DEFINE_IDTENTRY_SYSVEC(sysvec_uintr_kernel_notification)
sysvec_uintr_kernel_notification 会执行 uintr_wake_up_process(void)
- 遍历 upid 等待 list 查找待 有处理的 upid
- 设置 SN = 1, 启用接收.
- uintr_wake_up_process 唤醒后. upid 等待 list 清除当前 upid.
唤醒之后线程会执行 switch_uintr_return, 在这个函数中恢复上下文,并且再次 apic->send_IPI_self(UINTR_NOTIFICATION_VECTOR);
触发进入 uintr 处理流程. ^43673f
uintr_wait 的问题?
- 有概率无法在等待中返回 --> 目前无解,参考这个 commit. poc_v2 是否解决待验证.
- 没有定时 --> 参考 poc_v2, 添加定时器.