原文: https://community.arm.com/arm...
翻译: 修志龙 Zenon Xiu
Arm A-profile构架一个长久以来的局限性是:缺乏对non-maskable interrupt (NMI, 不能屏蔽的中断)的支持。但是,随着Arm A-profile 构架2021扩展的发布,arm增加了在CPU和GIC构架对NMI的支持。但是,到底NMI是什么,操作系统如何使用这个功能,为什么有些方式还可以屏蔽NMI但它还叫NMI?此博客更加细节地探索这些问题。
对NMI的需求
让我们从探索为什么需要这些特殊的中断和为什么他们被叫做‘不能屏蔽’ (non-maskable)说起。首先必须意识到所有的中断在特定的环境下都可以被屏蔽。比如,如果中断控制器完全关闭,没有中断可以传送给CPU。Non-maskable这个名称实际上指即使正常的中断被屏蔽的情况下,这类NMI中断还可以传送给CPU,但由对NMI的独立控制状态,这些控制更少地被标准的内核代码访问。在某些情况下能屏蔽所有中断的能力(包括NMI)也在由很长NMI历史的其他构架上可以看到。
为什么操作系统软件需要这类单独的只能在特地场景下才能被屏蔽的中断呢?有两个主要原因:
第一个明显原因是,中断可能被不小心留在被禁止的状态,使系统处于无法响应的状态。第一眼看,软件似乎很容易写成总会在中断禁止后再使能它,但事实本非如此。现代操作系统通常在各处有百万行代码直接操纵中断状态。中断处理程序可能导致同步异常,在中断禁止时,它会导致非线性代码和潜在的死锁。同时,考虑到第三方的运行在完全内核特权的驱动程序也可以操作中断状态。很明显简单的代码检查和静态分析不足以防止中断被留在禁止的状态。还有一些场景,快速传送中断比较关键,比如调试,跨CPU的同步和打热补丁。
第二个原因是,操作系统依赖于中断来做系统的系统分析。比如,AArch64 Linux的perf子系统依赖于PMU overflow中断。通过编程cycle counter在一定时间间隔overflow, 每个overflow中断都会被传送给CPU来进行代码路径的分析。Perf处理这些中断并采样这些运行进程的PC (program counter). 当仅有单个中断屏蔽可以给程序员控制时,任何在禁止中断状态下执行的critical section都无法被采样分析。用户通常抱怨的问题是他们的系统分析结果显示很多的是CPU时间都花在了local_irq_enable()上。这显然是不对的,因为这个函数是通过单条操作PSTATE.I 标志指令来实现的。这个误导信息是由PMU中断总是在IRQ重新被使能时传送给CPU的,perf总是采样到这个函数的PC。
在引入NMI构架支持之前,为了克服这个问题,Linux依赖于伪NMI,它利用了GIC的中断优先级的特性。Linux kernel将PMU overflow中断的优先级设置为高于其他中断。这需要重写arm64特定的中断禁止和使能函数为‘修改CPU interrupt priority mask (ICC_PMR_EL1)’,而不是直接操作CPU的PSTATE.I标志位。提示:arm构架可以在GIC中配置中断为IRQ或是FIQ中断,并做相应处理。虽然现在大多数的系统使用GIC,还是有可能使用其他中断控制器来构建基于arm的系统。
使用优先级实现的伪NMI在支持Linux性能分析时没问题,但不幸地引入了操作系统关键路径上的代码复杂度。比如,当KVM hypervisor进入guest VM时,hypervisor必须多个步骤小心处理。首先,优先级屏蔽是用来屏蔽非性能分析中断的,VM进入的代码路径也应被分析。第二,在配置CPU返回状态以便异常返回到VM之前,中断必须使用PSTATE.I屏蔽来阻止破坏异常返回状态。最后,优先级屏蔽必须减低,以便让host中断可以到晚些时候导致从VM退出。这些流程会导致大多数VM处理代码的开销,应该尽量避免,使用硬件NMI支持的话可以改善。
2021构架扩展对NMI的支持
Non-maskable的叫法有点不恰当,因为NMI是可以被屏蔽的。关键是要么有些中断必须可以独立于其他中断又独立的屏蔽控制,要么它必须避免软件可以简单直接地屏蔽这些特殊中断。有些操作系统仅需要单独的屏蔽控制,有些操作系统既要有单独的屏蔽控制又要有能阻止简单屏蔽中断的机制。
像arm这样的RISC构架,必须有可能在进入和退出异常时屏蔽所有的中断。这是因为异常信息被存在ELR_ELx和SPSR_ELx寄存器中。如果一个IRQ异常在CPU处理另一个异常的过程中被过早发送给CPU,那么就有可能冲掉ELR_ELx和SPSR_ELx寄存器的值。这些关键信息会丢失,从而无法恢复正常的代码流程。
这时需要提醒一下arm构架的另一个特性, PSTATE.SP. 当响应一个到ELx的异常时,CPU使用,特定的stack pointer - SP_ELx指向的内存去tack。软件可以在进入异常处理入口后的初始时刻切换为使用SP_EL0指向的内存去访问stack,比如在软件想为kernel执行重用用户空间线程stack pointer时。这使异常进入时的stack和执行线程上下文时是有的stack相互独立。可以通过设置特殊目的寄存器-SPSel来实现stack的切换。有些操作系统使用这种方式,有些不使用。比如,Linux运行在EL1或是EL2时,总是使用特定的stack pointer, SP_EL1 或是SP_EL2 (注:也就是运行Linux kernel时,SPSel设置为1),而将SP_EL0用做额外的寄存器。
2021构架扩展引入了将GIC中断配置带有特殊NMI优先级的能力,这个优先级严格高于其他优先级(这里先不考虑跨安全状态的优先级,比如,还是有可能配置安全非NMI中断有比非安全NMI有更高的优先级,更深入的NMI和中断安全状态的讨论超出了这个篇博客讨论的范围)。配置为NMI优先级的中断被称之为‘NMI’,它的屏蔽控是独立于其他中断。CPU构架允许操作系统通过在SCTLR_ELx系统寄存器里新引入的控制bit进行屏蔽。正常的非NMI中断,还是使用现有的PSTATE.I进行屏蔽。
两种屏蔽NMI的方式
第一种方式是利用现有的SP选择机制。在进入和退出异常时屏蔽包括NMI在内的所有中断,并阻止操作系统内核其他地方屏蔽NMI。工作方式是当使用特定SP_ELx stack pointer时屏蔽NMI,当操作系统完成了异常进入路径并切换stack pointer为SP_EL0时,NMI只有当stack pointer再次切换为SP_ELx时才能被屏蔽,这事实上阻止了操作系统直接屏蔽NMI。利用armv8.6-A构架扩展中的fine-grained trap控制,运行在EL2的hypervisor可以阻止运行在EL1的操作系统在除异常进入路径之外的其他地方切换stack pointer。
第二种屏蔽NMI的方式是简单地利用2021构架新引入的单独屏蔽bit, PSTATE.AllInt,和PSTATE.I bit一样,它在进入异常是被设置。PSTATE.AllInt独立于第一种stack pointer选择方式,它可以直接被软件设置或者清除,尽管运行在EL2的hypervisor还是可以trap运行在EL1的操作系统对PSTATE.AllInt的写操作。我们期望操作系统在需要性能分析(profiling)需要NMI时使用,这避免了基于优先级实现的伪NMI的复杂度。Arm预计Linux可以从这种屏蔽方式中获益。比如,当KVM将要运行一个VM时,VM进入路径可以通过设置PSTATE.I屏蔽正常的中断。然后KVM可以恰好在设置异常返回寄存器进入VM时设置PSTATE.AllInt。当在EL1运行VM时,目标为EL2的物理中断不被PSTATE屏蔽(他们只屏蔽虚拟中断)。通过使用PSTATE.AllInt, hypervisor可以避免如上面描述的必须临时管理优先级屏蔽和PSTATE.I的操作。
2021构架扩展也引入了一个GIC新的interrupt acknowledgment register-ICC_NMIAR1_EL1,它用来专门acknowledgment NMI中断。引入这个寄存器的目的是为了避免软件在不能处理中断的地方不小心acknowledgment NMI中断。比如这个场景:在PSTATE.I被设置的关键段(critical section)里,一个电平触发的NMI被触发,但是在 acknowledgment这个中断之前,NMI的电平被拉无效。
NMI的虚拟化支持
2021构架扩展支持NMI的虚拟化,CPU 构架支持EL1和EL2使用单独的屏蔽模式,模式的选择分别通过EL1的SCTLR_EL1和EL2的SCTLR_EL2控制,PSTATE中的屏蔽状态自然地被hypervisor在SPSR_EL2中保护和恢复。GIC的Virtual CPU interface与physical CPU interface有一样的单独的acknowledgment和识别虚拟NMI的能力(获取虚拟NMI interrupt ID).
运行在支持NMI硬件系统上的hypervisor软件需要被扩展以支持虚拟NMI。2021构架扩展包括NMI的虚拟化支持,它是通过扩展EL2 list 寄存器让其包含要发送给VM的虚拟中断来实现的。这些List寄存器现在有了一个新的NMI bit, 用来指示发送到virtual CPU interface的虚拟中断有NMI优先级。支持虚拟SGI (vSGI)直接注入的GIC实现也支持配置直接注入的vSGI带有 NMI优先级。
Hypervisor软件模拟虚拟GIC Distributor 和虚拟Redistributor,必须扩展hypervisor软件模拟来支持这个用来配置中断NMI优先级的新寄存器,最后,还要做个小小的修改来在virtual feature 寄存器中暴露系统对NMI的支持。
结论
总的来说,2021构架扩展引入的NMI支持提供了一个简单编程模型,来使能常见NMI使用场景。 在设计这个特性时,我们已经和我们的生态系统合作,保证我们能提供必要的自由度,能覆盖通用kernel和hypervisor 对NMI的不同需求。
更多信息
Arm Architecture Reference Manual for A-profile architecture
Arm Generic Interrupt Controller Architecture Specification
资源
GIC webpage
Learn the Architecture Documentation
A-Profile Architecture webpage