找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 1891|回复: 5

【处理器】详解用户中断

[复制链接]
发表于 2022-12-26 07:17:47 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×

前言

Intel手册更新了,出了一章叫做用户中断(User Interrupt)的东西。顾名思义,就是把中断直接发给用户模式,其理念可谓是越俎代庖,相当前卫了。
这么前卫的东西,Intel直接不给32位用了,我直接笑死。而且,兼容模式也用不了,必须是64位的程序才能用。
Linux已经推出User-Interrupt的支持了,目前主要用途其实也就是快速的通知其他用户线程罢了(据Benchmark,比Linux的eventfd快九倍,具体可以看这份PPT)。
AMD暂时没有对User-Interrupt的支持,但早晚会有。

架构

Intel在支持用户中断的处理器上,新增了数个MSR寄存器,并定义了多个指令来支持用户中断。
在支持用户中断的处理器上,中断被分为:普通中断(Ordinary Interrupts)和用户中断(User Interrupt)。普通中断就是指普通的,发往内核经IDT接收的中断。
处理器的cr4寄存器的第25位被定义为UINTR位,置位则表示操作系统启用用户中断。

中断接收

用户中断不使用IDT来接,而是由且仅由MSR来指定地址。
发生用户中断时,处理器不更改cs段选择子,仅修改rsprip,因此CPL不会发生变化。
在支持用户中断的处理器上,多出了一个标识位,叫做UIF,即User-Interrupt Flag,表示处理器当前是否能接收用户中断。置位表示能接,复位表示不能接。
用户中断不使用IDT接收,而是一个专门定义的MSR寄存器。
UIF置位外,接收用户中断还要满足以下条件:

  • 处理器在中断阴影(Interrupt-Shadow,即mov ss,xx, pop ss, sti等造成的一条指令下临时中断屏蔽)之外。
  • 处理器位于用户模式,即CPL=3
  • 处理器位于长模式,即CS.L置位。也就是说即便处理是64位的系统里,32位程序也用不了。
  • 处理器位于飞地(Enclave,即SGX保护区)之外。倒也正常,SGX基本上就要被Intel废了。

用户中断一般发生在一条指令完成的时候。但如果被打断的指令带有rep(z/nz)前缀,则中断会打断迭代,rip停留在这条带rep的指令之前,而rcx寄存器的值表示未完成的迭代次数,rsi,rdi寄存器则表示下一次迭代的线性地址。这样可以使得从用户中断返回时,继续完成迭代。
用户中断可以唤醒因tpause指令和umwait指令造成的处理器休眠状态。注意这两条指令均可以在用户态里使处理器休眠。
hlt指令造成的休眠发生在内核态,故用户中断无法唤醒hlt状态下的处理器。
同理,由INIT信号造成的Wait-for-SIPI睡眠状态的处理器也无法被唤醒。

当用户中断发生时,UIF会被强制复位以防止用户中断重入。
当用户态影栈机制被启用时,用户中断会向影栈压入发生中断位置的rip的值以防止ROP攻击。

指令

Intel定义了五个新指令:senduipi, uiret, clui, stui, testui

senduipi指令

senduipi即Send User Inter-Processor Interrupt,用于发送一个处理器间的中断。其格式为:

senduipi reg64

只有一个寄存器操作数,恒定为64位。重置操作数大小的前缀(如66h)会被忽略。
这个操作数表示一个发送UIPI时UITT的索引值。注意,这不是用户中断的向量。
GCC11里添加了_senduipi这个内置宏来让C语言使用这条指令。
当发送的UIPI无效时,会抛出#GP异常。

uiret指令

uiret即User-Interrupt Return,用于从用户中断处理中返回。其格式为:

uiret

没有操作数,其用途就是返回到用户中断发生的地方恢复执行,包括了rsp,riprflags寄存器。
此外,uiret会强制置位UIF位以继续接收用户中断。
当用户态影栈机制被启用时,会从影栈里弹出发生中断位置的rip的值,并判断栈上的返回值。若不匹配,则产生#CP异常。

clui指令

clui即Clear User Interrupt Flag,用于屏蔽用户中断。其格式为:

clui

没有操作数,它会将UIF复位。

stui指令

stui即Set User Interrupt Flag,用于解除对用户中断的屏蔽。其格式为:

clui

没有操作数,它会将UIF置位。
但和sti性质相反的是:sti解除屏蔽时,要到sti的下一条指令完成后才会完成对中断屏蔽的解除,而stui指令会在该指令完成后立即解除中断。

testui指令

testui即Test User Interrupt Flag,用于获取UIF的值。其格式为:

testui

没有操作数,它会把UIF的值写入RFLAGS.CF位中。因此需要结合setccmovcjcpushf+pop-reg64指令使用。
此外它还会将rflagszf,af,of,pf,sf位复位。因此如果在testui后仍需要使用这些位,请先用pushf指令保存之。

MSR寄存器

Intel定义了六个新MSR寄存器:IA32_UINTR_RR (MSR-Index=0x985), IA32_UINTR_HANDLER (MSR-Index=0x986), IA32_UINTR_STACKADJUST (MSR-Index=0x987), IA32_UINTR_MISC (MSR-Index=0x988), IA32_UINTR_PD (MSR-Index=0x989) 和 IA32_UINTR_TT (MSR-Index=0x98A)

UIRR寄存器

User-Interrupt Request Register,即IA32_UINTR_RR (MSR-Index=0x985)这个MSR寄存器,是一个用来向当前处理器发送用户中断的MSR。
该寄存器是一个位图,置位的项表示要发出去的用户中断的向量号。比方说写入0x11这个值就表示发出去的用户中断的向量号是1和4。
处理器在准备发布用户中断时,就会向该MSR写入值。

UIHANDLER寄存器

User-Interrupt Handler,即IA32_UINTR_HANDLER (MSR-Index=0x986)这个MSR寄存器,表示在接收用户中断时,rip寄存器的值。
值得注意的是,如果写进去了一个超出处理器支持范围的合法线性地址,那就会报#GP异常。

UISTACKADJUST寄存器

User-Interrupt Stack Adjustment寄存器,即IA32_UINTR_STACKADJUST (MSR-Index=0x987)这个MSR寄存器,表示在接收用户中断时,处理器如何修改rsp寄存器的值。

含义
0 该位置位时,从该MSR里加载rsp寄存器的值
1-2 保留不用
3-63 用户中断处理函数的rsp的高61位;低3位复位以在8字节上对齐

虽然表格里说“低3位复位以在8字节上对齐”,但实则处理器会将低4位复位以在16字节上对齐。

UINV与UITTSZ寄存器

User-Interrupt Notification Vector和User-Interrupt Target Table Size寄存器以IA32_UINTR_MISC (MSR-Index=0x988)寄存器表达。这一点和普通中断的性质一样,栈地址会放在16字节对齐的位置上。

含义
0-31 该32位表达UITTSZ寄存器的值
32-39 该8位表达UINV的值
40-63 保留不用

UITTSZ表示UITT的最大索引值,也就是senduipi指令的操作数允许的最大值。
UINV表示用户中断的通知向量。

UITT寄存器

User-Interrupt Target Table寄存器,即IA32_UINTR_TT (MSR-Index=0x98A)这个MSR寄存器,表示UITT这张表的基址。
每个UITT表项占128位,结构如下:

含义
0 Valid位,表示是否有效。无效时,senduipi指令会抛出#GP异常
1-7 保留不用
8-15 用户中断向量
16-63 保留不用
64-127 UPID基址,但由于UPID需要按64字节对齐,故64-69位必须复位

UPID寄存器

User Posted-Interrupt Descriptor,即IA32_UINTR_PD (MSR-Index=0x989)这个MSR寄存器,用于描述如何发送和接收中断。
UPID仅占16个字节,实在是不明白为什么要按64字节对齐。

含义
0 ON位,即Outstanding Notification位。若该位置位,则表示有一个或多个未完成的中断通知
1 SN位,即Suppress Notification位。若该位置位,则表示中断通知应当被按下不表
2-15 保留不用
16-23 NV位,即Notification Vector。该位表示通知目标处理器时使用的普通中断向量
24-31 保留不用
32-63 表示接收通知的逻辑处理器的x2APIC号。若处理器未启用x2APIC,则以40-47位表示APIC号
40-47 表示接收通知的逻辑处理器的APIC号。若处理器启用了x2APIC,则以32-63位表示x2APIC号
64-127 PIR位图,即Posted-Interrupt Request,置位则表示该用户中断的向量号存在中断请求

不论UITT还是UPID,原则上OS应当把它们放置在内核内存里。
但由于熔断漏洞(即CVE-2017-5715)会打破页表的用户内存和内核内存的隔离能力,OS的熔断漏洞补丁会为一个进程建立两套页表,第一套页表完整映射了内核内存和进程的用户内存,而第二套页表仅映射了进程的用户内存和一些用户与内核交界处(如系统调用函数,中断处理函数等开头)的内存。
用户态代码运行时,则使用第二套页表;进入内核时,则使用第一套页表。确保用户态的代码运行时,内核内存不被映射,这样就修复了熔断漏洞。
很明显,UITTUPID应当在第二套页表里也有所映射。

扩展状态

最初的时候,扩展状态只有x87 FPU,因此用f(n)savef(n)rstor指令来保存恢复FPU的状态即可。
后来出了SSE,于是新增了fxsavefxrstor指令引用一个512字节的内存区域实现保存与恢复。
再后来则出现了AVX,512个字节塞不下AVX拓展出来的ymm寄存器,因此新增了xsavexrstor指令来把所有ymm寄存器的高128位塞到后面去。
同时也意识到以后还会增强SIMD,不能再为此新增指令了,故xsavexrstor指令带了一个隐含的操作数:edx:eax。这是一个64位的值,表示一个掩码。
edx:eax & xcr0的某一位被置位时,则xsavexrstor会保存恢复该扩展状态。比如当这个逻辑与运算的结果是7的时候,就保存恢复FPU(第0位置位),SSE(第1位置位)和AVX(第2位置位)的状态。
效果不错,以至于出了AVX-512的时候,zmm寄存器也能用xsavexrstor指令来保存恢复。
但后来Intel出了Processor Trace,AMD出了Light-Weight Profiling,以及Control-Flow Enforcement,还有现在的User Interrupt这些新增了内核MSR的东西。
xsavexrstor在用户态就能用,允许这两条指令来保存恢复的话显然会越权。
但如果用rdmsrwrmsr来一个一个保存恢复就效率太差了,故新增了xsavesxrstors指令来保存恢复这些MSR。这两条指令仅在内核态可用。
为了xsavesxrstors,处理器新增了Extended Supervisor State Mask,即IA32_XSS (MSR-Index=0xDA0)这个寄存器。
xsavesxrstors的逻辑变成了当edx:eax & (xcr0 | xss)的某一位置位时,保存恢复其状态。
其中的第14位就用于表示xsavesxrstros指令会保存恢复用户中断的状态。这个状态包括新增的六个MSR的值,但不包括UIF位的值。

总而言之,操作系统可以借用xsavesxrstors这两条指令来负责进线程的上下文切换。

用户中断的发送与接收的逻辑

先说UIPI吧。
执行senduipi时,处理器读取该指令指定的寄存器操作数,以该寄存器的值作为索引,访问UITT这张表中某一项。
如果这个索引超过了限制(即IA32_UINTR_MISC这个MSR中的UITTSZ),或者指定的UITT表项中的Valid位被复位,就会抛出#GP异常表示无法发送。
如果任意一个保留位(包括表项指定的UPID)被置位,也会抛出#GP异常。
senduipi会在UPID里的PIR域里,根据UITT表项的用户中断向量进行置位。(由此可以看出,多个UITT表项可以共用一个UPID
UPIDSN位和ON位均被复位,则将UPIDON位置位,并且确认要发送通知。
注意以上的内存操作全部以内核模式权限进行的原子操作,即便senduipi这条指令是在用户态里完成的。
若确认了要发送通知,则以UPID的32-63位作为目标处理器的x2APIC号,并以UPIDNV位作为中断向量,发送普通IPI

接下来是接收。当另一个处理器的Local APIC接收到一个外部中断时,会判断中断向量与接收者处理器上的IA32_UINTR_MISCUINV是否相等。
若不等,则表示这就是纯粹一个外部中断,或者其他用途的IPI,处理器会走IDT来接收中断。
若相等,则表示这是个用户中断,此时接收者的Local APIC会自动发出EOI(即End-of-Interrupt)信号,表示普通中断已被处理。然后处理器走用户中断的专有途径来处理用户中断。
接收者识别出用户中断后,会接着对自己的UPID里的ON位进行一个复位操作,然后读取并清零UPID里的PIR位,并将读出来的PIR位以逻辑或写入UIRR寄存器中。
当前处理器里未处理的用户中断会反映在UIRR里。当处理器具备接收用户中断的条件时,若UIRR不为0,则产生用户中断。

硬件虚拟化

由于目前只有Intel提出了用户中断,故硬件虚拟化的相关支持目前也就只有Intel VT-x才有。

新增的VMCS字段

Intel在VMCS里新增了一个Guest UINV字段,该字段是一个16位Guest状态字段(注意UINV本身是8位的,但VMCS的最小粒度是16位)。
在VM-Entry控制字段里新增了Load UINV字段,表示VM-Entry时,是否根据VMCS里新增的Guest UINV字段加载UINV状态。
在VM-Exit控制字段里新增了Clear UINV字段,表示VM-Exit时,是否直接清掉UINV状态。
Load UINV字段置位时,处理器在VM-Entry会检查Guest UINV字段的高八位是否为零

外部中断

若VMCS指定拦截外部中断,则当Local APIC接收到外部中断时,不识别其是否为用户中断,直接当成外部中断进行拦截并触发VM-Exit。

MSR位图

在MSR位图中拦截用户中断的MSR操作不会影响处理器本身访问这些MSR。就好比拦截LSTAR这个MSR的读取不影响处理器syscall指令的执行。

虚拟中断

当VMCS中启用了虚拟中断处理的时候,不由Local APIC处理接收到的中断,而是由Intel VT-x定义的虚拟中断处理方式来负责识别处理用户中断。其具体过程大同小异。
值得一提的是,事件注入的行为也会发生变化。当虚拟机管理器向开启了用户中断的vCPU注入一个外部中断时,处理器会根据向量号识别该中断是否为用户中断,并进行相关处理。

EPT

当用户中断的行为自身触发了EPT相关VM-Exit(如EPT-Violation)时,Exit Qualification字段的第16位会被置位,表示这是异步于指令执行的VM-Exit。

OS设计

本章简单谈一谈设计OS时,如何支持用户中断。

OS可以选定一个空闲的普通中断向量,该中断向量会被处理器视为用户中断处理。
OS可以给出一个系统调用的接口,用于创建删除“用户中断控制器”对象。
该对象有一个UPID,一个线程在接收中断:即一个中断处理函数和一个中断处理栈。
OS可以提供一个接口让线程注册一个中断处理函数(即IA32_UINTR_HANDLER),并注明是否使用隔离的栈,用多大的隔离栈(即IA32_UINTR_STACKADJUST)。
每个控制器只可注册一个线程的中断处理,除非逻辑上senduipi的接收者可以不是唯一的。
OS可以为每个线程创建一个UITT表,并提供注册发送器的系统调用。线程根据控制器注册了发送器后,系统会在UITT表上分配一个空闲的UITT项,填入控制器的UPID,并将该索引返回给线程。
线程就可以用senduipi指令来给别的线程发UIPI了,操作数里填注册发送器时系统返回的值即可。

在每个线程的扩展状态(即xsavesxrstors管理的状态)里:

  • IA32_UINTR_RR, IA32_UINTR_HANDLER, IA32_UINTR_STACKADJUST, IA32_UINTR_TT是独立的,每个线程都各管各的。
  • IA32_UINTR_PD取决于线程接收用户中断的控制器。
  • IA32_UINTR_MISC由系统设定,一般所有线程均使用同一个中断向量号,否则管理普通中断的向量号会极为混乱。最大UITT的索引号可以视情况给予弹性设置。

在OS调度线程的时候,可以检测哪些非活动线程的UIRR不是零,然后优先调度到CPU上来处理用户中断。
最后别忘了用testui保存线程的UIF位并且用stuiclui恢复之。

目前还没有外部设备给处理器发送用户中断的文档。猜测如下:

  • 配置中断控制器(注意是I/O APIC之类的硬件中断控制器)时,需要把中断向量配置为UINV
  • 中断控制器需要能让IRQ选择UPID,否则接收对象会存在多义性。

总结

其实我并没有搞到支持用户中断的CPU,本文描述的一切都是根据阅读手册来的。因此不可避免地会出现错误,以后搞到一块支持用户中断的CPU再来勘误。
总之目前来看,用户中断的应用仅限让硬件来加速事件型的线程间通信。外源性的用户中断支持还得等等。

回复

使用道具 举报

发表于 2022-12-27 12:58:49 | 显示全部楼层
其实介绍这么多,不如先来一段演示代码,然后解释每条指令其作用更能让人理解
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2022-12-27 21:35:44 | 显示全部楼层
Ayala 发表于 2022-12-27 12:58
其实介绍这么多,不如先来一段演示代码,然后解释每条指令其作用更能让人理解 ...

没有CPU,无法演示。
回复 赞! 靠!

使用道具 举报

发表于 2023-1-9 22:49:16 | 显示全部楼层
这玩意会不会像SGX一样,过几代就消失?
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2023-1-10 02:54:00 | 显示全部楼层
Golden Blonde 发表于 2023-1-9 22:49
这玩意会不会像SGX一样,过几代就消失?

得有替代的玩意它才会消失。SGX没了是因为AMD的SEV比SGX设计的好于是Intel仿了个TDX出来。
回复 赞! 靠!

使用道具 举报

发表于 2023-1-31 08:24:57 | 显示全部楼层

感谢分享制作,
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-26 22:53 , Processed in 0.039686 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表