RISC-V ARM x86中断机制对比:一条主线看懂三大架构差异
2026/9/20 11:55:18 网站建设 项目流程

做嵌入式底层或者系统软件开发的朋友,估计都有过这种体验:今天调RISC-V核的PLIC,明天改ARM核的GIC,后天又得去抠x86的APIC。每次切换架构,都感觉像是重新学了一遍中断。但实际踩过几个坑之后你会发现,这三种架构的中断流程,本质上就是同一条主线上的不同变体。这篇文章我把这条主线抽出来,用我实际调试过的经验带大家走一遍,看明白它们到底哪里一样、哪里不一样。

1. 中断流程的主线:五步走完一次完整中断

不管是什么架构,一次完整的中断处理,从硬件到软件,绕不开这五个环节。

1.1 主线全貌:事件源、控制器、CPU响应、软件分派、现场恢复

我先把这个主线画出来。一次中断从发生到处理完毕,大致经历这么几个阶段:

  1. 外设产生中断请求信号(比如UART收到一个字节、DMA搬运完成)。
  2. 中断控制器对这个请求进行仲裁、优先级排队、屏蔽/使能控制,最终决定把哪一个中断送给CPU。
  3. CPU响应中断:保存当前的执行状态(至少是PC和关键状态寄存器),关掉中断使能位,跳到中断入口地址。
  4. 软件在中断入口里读取中断控制器,搞清楚是谁触发的中断,然后跳转到对应的处理函数。
  5. 处理完毕,恢复现场,执行中断返回指令,CPU接着执行被打断的程序。

这条主线,RISC-V、ARM、x86全都逃不掉。区别在于每一步的具体实现方式差别很大。

为什么我要强调这条主线?因为很多初学者看手册最容易懵的就是这一步:RISC-V手册里讲PLIC怎么工作,ARM手册里讲GIC怎么配寄存器,x86文档里讲APIC和IDT怎么设置,看起来完全是三套东西。但你心里只要装着这条主线,看手册的时候就清楚自己在找什么——无非就是这几个问题:谁在管中断源?怎么标记优先级?中断向量怎么给的?现场谁负责保存?返回指令长什么样?

1.2 三个架构在这条主线上各自的位置

我用一句话概括三个架构的定位差异:

  • x86:历史包袱最重,但设计最“傻瓜化”。中断控制器和CPU深度绑定,CPU自己接管了大量的现场保存和自动跳转工作。
  • ARM:中断控制器(GIC)是独立IP,CPU核和中断控制器解耦。现场保存主要靠硬件自动完成一部分,但比x86要更依赖软件。
  • RISC-V:走极简路线,中断控制器(PLIC/CLINT)也是独立IP,但CPU核只提供最基本的中断响应机制,现场保存、中断源识别这些几乎全靠软件来做。

这个定位差异直接决定了你在三种平台上写中断处理代码时的工作量完全不一样。我刚开始从ARM转RISC-V的时候,最不适应的就是这一点:以前GIC帮你做了很多事情,到了RISC-V上,全部要自己来。

2. 外设拍门到CPU开工之间:中断控制器在忙什么

外设产生的中断请求不是直接连到CPU引脚上的——至少现代高性能处理器都不是这样。中断信号会先汇聚到中断控制器,由它来做仲裁和分发。

2.1 ARM的GIC:分层设计的“物业管家”

ARM的中断控制器叫GIC(Generic Interrupt Controller)。从GIC-400到GIC-500系列,再到现在ARMv9平台上的GIC-700,架构思路一脉相承:分成Distributor和CPU Interface两部分。

Distributor负责管理所有中断源:SPI(共享外设中断)、PPI(私有外设中断)、SGI(软件触发中断)。它处理的工作是使能/屏蔽、优先级设置、触发方式配置(电平触发/边沿触发),然后把选中的中断送往CPU Interface。

CPU Interface往简单了说就是每个CPU核私有的“门铃”。它负责对当前CPU核进行中断屏蔽(PRIORITY MASK)、抢占控制(Preemption),当有中断需要处理时,拉高CPU核的IRQ信号。

实际调试中要注意的一个点是:GIC的优先级数值方向和大部分人的直觉相反——数值越小,优先级越高。我之前在论坛上看到不少人配置了GICD_PRIORITY寄存器,然后发现中断行为不符合预期,最后查出来就是优先级搞反了。这个真是经典误区。

2.2 RISC-V的PLIC和CLINT:麻雀虽小,边界清晰

RISC-V这边,中断控制器不是CPU核自带的,而是由SoC设计者自己选。但业界事实标准是SiFive提出的PLIC(Platform-Level Interrupt Controller)加CLINT(Core Local Interrupt器)的组合。

PLIC管的是外部中断(external interrupts),负责所有外设中断源的优先级仲裁、使能、claim/complete流程。CLINT则管理定时器中断(timer interrupts)和软件中断(software interrupts),也就是机器模式下的mtime比较器触发的中断和跨核触发的软件中断。

有意思的是PLIC处理的流程和GIC有个本质区别:GIC在硬件上就把中断分发到了目标CPU核,而PLIC是所有CPU核共享一套中断输入,具体谁来处理,取决于软件让哪个核去claim。PLIC的claim机制是这样的:当一个外部中断被触发,CPU进入中断服务程序后,软件去读PLIC的claim寄存器,读完这个寄存器,PLIC才把该中断标记为“已被接收”,然后仲裁下一个最高优先级的中断。

2.3 x86的APIC:本地APIC和IOAPIC的老夫老妻

x86的中断控制器体系看着复杂,拆开其实也简单。IOAPIC负责收集来自总线的外部中断(相当于GIC的Distributor/PLIC),LAPIC(Local APIC)集成在CPU内部(相当于GIC的CPU Interface/CLINT)。

IOAPIC把中断映射成中断向量,然后通过总线发给目标CPU的LAPIC。LAPIC收到后,结合自身的TPR(Task Priority Register)、PRI(Processor Priority)做最终裁决,决定是否向CPU core提交中断。

x86这里值得单独拎出来说的是IRQ和中断向量的概念区分。老式的8259A PIC时代,IRQ号和向量号是一一对应的(IRQ0对应向量0x20这种),到了APIC时代,IOAPIC的红区表(Redirection Table)可以自由地将任意IRQ映射到任意向量。这个灵活性很强大,但也要求软件在初始化时把这层映射关系建立清楚,否则后面驱动申请IRQ时会一片混乱。

2.4 三个控制器的横向对照

我做了个表格来对照三家中断控制器的架构分工:

功能ARM GICRISC-V PLIC/CLINTx86 APIC
外部中断收集DistributorPLICIOAPIC
CPU侧中断提交CPU Interface直接送IRQ引脚(PLIC无核内接口)LAPIC
定时器/软件中断SGI via Distributor / Generic TimerCLINTLAPIC Timer / IPI
优先级方向数值越小优先级越高数值越小优先级越高(默认)TPR值越大屏蔽更高级别中断
中断源ID获取读GICC_IAR读PLIC claim寄存器读向量号(由IDT直接映射)
中断完成确认写GICC_EOI写PLIC complete寄存器写LAPIC EOI

这个表格建议收藏。实际工作中在两个架构之间切换时,这几个对应关系最容易搞混。我自己就曾经在RISC-V上顺手写了GIC风格的EOI操作,结果发现PLIC的complete寄存器和GIC的EOI语义还不完全一样——GIC是先EOI后做别的事情,PLIC是先完成处理再complete,顺序别搞反。

3. CPU响应这道坎:从取向量到硬件现场保存

中断控制器决定“该你上了”,接下来就看CPU核自己怎么接住这个中断。

3.1 RISC-V的mtvec/stvec:向量表的极简实现

RISC-V的中断入口设置非常直接:写mtvec(机器模式)或stvec(监管模式)寄存器,指定入口地址。这里有两种模式:

  • 直接模式(Direct):所有中断都跳到同一个地址,软件进来之后再读mcause/scause寄存器判断是中断还是异常、是什么类型。
  • 向量模式(Vectored):PC跳转到mtvec + 4 × cause的位置。因为是每个异常原因对应一个入口,所以可以做到中断和异常快速分派。

向量模式听着不错,但在实际SoC设计里用的反而不多。为什么?因为外部中断经过PLIC之后,CPU看到的只是machine external interrupt这一个cause值,所有的外部中断源还是得靠软件去PLIC那边查。所以向量模式省掉的分派工作有限,反而因为要保证各个入口之间的距离和跳转逻辑,代码布局上更麻烦一些。RISC-V的向量表本身很简单,它就是一组跳转指令。

真正需要特别注意的反而是RISC-V的现场保存:几乎没有硬件辅助。x86的中断响应会自动把EFLAGS、CS、EIP压栈;ARM会保存LR和SPSR;RISC-V这边只做了两件事:关中断(mstatus.MIE清0)、跳到入口地址。至于通用寄存器、返回地址,统统靠软件自己压栈。

这就意味着RISC-V的中断入口代码必须手写汇编save/restore逻辑。第一次写的时候很容易漏掉某个寄存器,尤其在带FPU或者向量扩展(V扩展)的核上,还得考虑是否保存浮点状态。这个我在后面避坑部分展开讲。

3.2 ARM的向量表和硬件自动保存

ARM的中断响应在Cortex-A系列上跟RISC-V的逻辑差不多,也是查向量表。向量表在VBAR寄存器指向的地址上,每个异常类型对应一个入口。不过ARM在中断响应时序上比RISC-V多做了一步:自动保存返回地址到LR_irq、保存CPSR到SPSR_irq

这意味着你在ARM中断入口里可以直接用SUBS PC, LR, #4这种指令实现返回,因为LR里的值已经被硬件调整过了。写ARM汇编中断服务程序时,这是个很实用的特性。

到了ARMv8-A 64位时代,异步中断(IRQ/FIQ)的入口逻辑有了变化:异常向量表在VBAR_EL1指向的地址,每个异常类型占据0x80字节的入口空间。同步异常和异步异常的保存行为也有一些细节差异,但返回地址还需要根据异常类型手动决定是否减量。

Cortex-M系列做的更彻底。它把整个中断现场保存都硬件化了:响应中断时自动压栈xPSR、PC、LR、R12、R3-R0,出栈也自动完成。这也是为什么Cortex-M的单片机开发里经常可以直接用C写中断处理函数,连汇编都不用碰。

3.3 x86的IDT:一张表管所有中断和异常

x86走的是另一种路线:用一张中断描述符表(IDT)把256个向量全部定义好。每个向量对应一个门描述符(Interrupt Gate或Trap Gate),里面记录了目标代码段选择子和偏移量。

CPU收到中断后做的事很“重”:

  • 根据向量号查IDT,获取入口地址。
  • 自动保存当前EFLAGS、CS、EIP到栈上(如果是跨特权级,还会自动切换栈并保存SS和ESP)。
  • 如果是Interrupt Gate,硬件自动关中断(清除IF位)。

这个响应过程的含金量在于“自动”两个字。尤其跨特权级时的TSS任务切换机制,在x86上是由CPU硬件完成的,软件不需要管用户态到内核态的栈切换。这也是为什么x86的syscall/interrupt路径可以做得很快。

不过x86这里有个历史包袱:IDT里的Interrupt Gate会自动清IF位,Trap Gate不会。很多人在写内核的时候误用了Trap Gate,导致中断嵌套问题,排查半天查不出来。这个细节手册里有,但不到踩坑时真不会注意。

3.4 三种CPU响应机制的差异对照

环节RISC-VARM (Cortex-A)x86
入口地址配置mtvec/stvec寄存器VBAR寄存器IDTR+IDT表项
向量表结构直接模式/向量模式每组4种异常类型,每类0x80字节256个门描述符
硬件自动保存几乎无,仅关中断并跳地址保存LR和SPSR(异常模式),其他靠软件保存EFLAGS、CS、EIP(跨特权级还保存SS/ESP)
关中断时机响应时由硬件清MIE/SIE由软件在入口处清除(或者用GIC的优先级掩码控制)Interrupt Gate自动清IF;Trap Gate不清
返回指令mret/sret手动从LR减量返回(或eret)iret/iretq

4. 现场保护的幕后细节:为什么说全寄存器保存是个技术活

中断处理的另一半江山是现场恢复。很多初学者以内核崩溃就只知道去查中断处理本身,其实问题经常出在保存/恢复的不对称上。

4.1 RISC-V的汇编保存/恢复模板

RISC-V因为没有硬件自动压栈,所以需要软件自己保存所有会用到的寄存器。最典型的就是sw t0, offset(sp)这样的指令逐条保存。我见过最简单也最容易出问题的写法是这样的:

// 错误示例:只保存了部分寄存器,且没有调整栈帧 save_context: csrr t0, mepc sw t0, (sp) sw ra, 4(sp) // ... 处理中断 ...

这个写法的问题很明显:如果中断处理过程中函数调用发生,ra会被覆盖,返回到错误的地方;栈指针也没调整,很容易把上下文写进同一个位置。

正确的做法是:先分配栈空间,然后按规则保存所有需要保留的寄存器。这里的关键是弄清楚你的编译器和ABI规定了哪些寄存器是caller-saved,哪些是callee-saved。中断服务程序不是一个普通函数,它要保证被打断的上下文原封不动地恢复。

补充一点关于FPU的:如果你的RISC-V核带有F扩展或D扩展,还要考虑是否保存浮点寄存器,以及是否保存fcsr(浮点控制状态寄存器)。如果中断处理程序根本不用浮点运算,可以通过设置mstatus.FS为off来跳过保存,但前提是你能保证整个中断路径里任何函数包括库函数都不会碰浮点寄存器。这个保证在实际工程里很难做到,所以我一般建议直接全套保存,代价是中断延迟增加一些周期。

4.2 ARM的banked寄存器设计:少压栈的硬件红利

Cortex-A系列的IRQ模式和FIQ模式各自有独立的SP和LR。这意味着在IRQ模式下,你不需要像RISC-V那样在中断一开始就把原模式的SP和LR压栈保存,因为硬件已经帮你备好了专门的IRQ模式的SP和LR。

但这个设计也有坑:IRQ模式下只有几个banked寄存器,通用寄存器R0-R12还是共享的。所以如果中断处理程序要使用R0-R12,仍然需要自己保存。FIQ额外多banked了R8-R12,所以FIQ的中断延迟可以做到更低——这是FIQ设计之初的目标,但现在大部分系统直接用IRQ就够了。

Cortex-M就完全不一样了,它使用的是线程模式+处理模式两套栈指针(MSP/PSP),中断响应时硬件自动压栈8个寄存器,出栈也自动。所以在Cortex-M上写中断,大部分时间可以用C直接写,不用手刨汇编。

4.3 x86的中断栈和TSS:从Ring3到Ring0的自动切换

x86中断响应里最特殊的就是栈切换逻辑。如果中断发生在用户态(Ring3),CPU会从TSS中读取Ring0的栈指针(SS0:ESP0),然后自动压入用户态的SS、ESP、EFLAGS、CS、EIP。这个动作完全硬件化,软件不需要参与。

这个设计极大简化了内核的入口代码。你在Linux内核的entry_64.S里能看到,真正进入C代码之前,其实已经有一套完整的pt_regs结构在栈上了。你只需要在这个基础上继续保存其它寄存器(R8-R15等),因为x86的通用寄存器数量多,硬件只自动保存了最核心的几个。

另一个x86特有的操作是中断返回时的栈平衡检查。iretq指令会同时弹出RIP、CS、EFLAGS(以及可能的RSP和SS),如果入口和出口的栈操作不对称,iretq会弹出一个非法的RIP直接崩溃。这类问题比ARM上的LR减量错误更隐蔽,因为崩溃的症状经常是随机的。

5. 中断返回:一条指令和它背后的物联网

中断返回指令看似简单,实际暗藏不少门道。那个老问题——为什么ARM的返回要SUBS PC, LR, #4,x86的返回要iret,RISC-V的返回要mret——里面装的是历史。

5.1 ARM的PC=LR−4之谜

ARM的经典中断返回是这样:SUBS PC, LR, #4。为什么减4?因为ARM流水线的原因,当CPU在执行指令时,PC实际指向的地址是当前指令地址+8(ARM状态)或+4(Thumb状态)。中断发生时,LR保存的值可能是PC+4或者PC+8,不同异常类型有差异。IRQ的返回需要减4才能回到被打断的那条指令的正确地址。

这个“减多少”的规则是处理器手册里写死的,不同模式不一样:IRQ减4,FIQ减4,Prefetch Abort减4,Data Abort减8,SVC不用减。刚接触ARM汇编的人在写中断返回时经常记混,我一开始也是这样,后来总结了一个口诀:跟流水线有关的都要减,跟同步异常有关的按异常类型特定处理。

ARMv8-A 64位上,eret指令直接从ELR_ELx寄存器恢复PC,不再需要手动减量,因为ELR_ELx在异常响应时已经被硬件设置成正确的返回地址了。所以新架构上这个历史坑已经没了,但如果还在维护32位ARM的代码,这个减4的规则还是得烂熟于心。

5.2 x86的iret/iretq:特权级变换的开关

x86的iret指令不只是恢复PC,它会同时恢复EFLAGS和CS寄存器,因此可以从Ring0回到Ring3,反之也行(但实际退出到用户态返回时候用的是sysret/sysexit这类更快的指令)。

iret在恢复过程中有一个很微妙的行为:如果栈上的CS值表明要跨特权级返回,CPU会同时恢复用户态的SS和ESP,并且从内核栈上弹出这些值。这就意味着内核栈上的内容布局必须严格匹配CPU预期,不能有半点差错。否则CPU会尝试从一个错误的栈位置弹数据,然后产生#GP异常。

现代x86内核里,中断返回路径通常会检查一些条件来决定用iretq还是优化过的sysretq路径。普通中断结束用iretq,系统调用返回用sysretq。这两个路径的栈处理细节不一样,别混。

5.3 RISC-V的mret/sret:CSR三件套的写回

RISC-V的中断返回指令mret做三件事:

  • mepc恢复PC
  • 恢复mstatus中的MIE(中断使能)
  • 根据mstatus.MPP恢复之前的工作模式(Machine/Supervisor/User)

这三件事是硬件一次性完成的,所以你在RISC-V中断里保存和恢复mepcmstatus这些CSR时要特别小心。如果在中途修改了mstatus却没有正确保存/恢复,mret会把一个错误的值写回,轻则中断使能状态不对,重则模式错乱直接跑飞。

这里有一个很实际的经验:RISC-V的mret不会自动关中断,它只是恢复中断使能状态。所以如果你的中断入口在保存上下文过程中开了中断,返回时就会产生嵌套中断的复杂情况。一般建议整个过程保持中断关闭,直到所有现场都恢复好,最后执行mret一次到位。

6. 从零到能跑的中断初始化:三种架构的启动差异

理论讲完,落到实操上。三者的初始化步骤有着完全不同的工作量分配。

6.1 ARM:GIC初始化+向量表配置

ARM中断初始化的标准流程大概是:

  1. 设置VBAR指向一组已定义好入口的异常向量表。
  2. 初始化GIC:Distributor使能、CPU Interface使能。
  3. 配置中断路由(将SPI路由到目标CPU)、优先级和触发方式。
  4. 在GICD_ISENABLER中使能特定中断源。
  5. 开CPU的IRQ总中断(CPSR的I位清零)。

这里面最容易遗漏的是GIC的“使能顺序”问题。不同版本的GIC对Distributor和CPU Interface的使能顺序有讲究。GIC-400上通常需要先配好Distributor和CPU Interface寄存器再使能,否则某些初始化状态下的脏中断可能会被打进来。这个问题在Linux内核的GIC驱动里有很多注释,侧面印证了它确实是经典坑。

另一个坑是GIC的SGI踩踏。在多核系统中,发送SGI时如果没有保证内存屏障,接收核可能读不到发送核之前写好的数据。ARM的内存模型里,SGI和你正常的内存访问之间需要显式加屏障,否则会出现“中断到了,数据还没到”的奇怪时序。

6.2 RISC-V:PLIC只有一套极简的开关

RISC-V的中断初始化,步骤上看起来很简单:

  1. 配置mtvec指向入口。
  2. 初始化PLIC:在PLIC的PENDING/ENABLE寄存器里使能对应中断源。
  3. 设置PLIC优先级寄存器,以及目标CPU的阈值寄存器(threshold)。
  4. 开全局中断:设置mstatus.MIE,以及mie寄存器里的MEIE。
  5. mret或直接启动第一个任务时自然使能中断。

看起来比ARM少很多,但RISC-V的这些步骤在全软件栈上给你挖了额外的坑:PLIC的ENABLE寄存器是分上下文的。多核系统里,每个hart(硬件线程)有自己独立的PLIC上下文,你在hart0上使能了中断,却跑到hart1上去等中断,那肯定等不到。

还有就是PLIC的优先级和阈值需要在中断使能之前设置好。因为PLIC是根据当前运行的优先级来仲裁的,阈值寄存器设置不当,可能出现低优先级中断一直无法触发,或者高优先级中断被低优先级任务持续打断的极端情况。我在调试一个多核任务系统时遇到过类似问题,查了半天PLIC寄存器,最后发现是阈值设错了。

6.3 x86:IDT加载+8259A/APIC模式切换

x86的初始化流程,对现代操作系统来说是这个样子:

  1. 构造IDT,把中断门/陷阱门填充好,通过lidt加载IDTR。
  2. 如果是兼容老式8259A的环境,需要屏蔽/重映射PIC。
  3. 初始化Local APIC:设置SPIV(Spurious Interrupt Vector Register)、LDR/DFR(逻辑/扁平目的地)、TPR。
  4. 配置IOAPIC的红区表(Redirection Table),把设备IRQ映射到想要的向量号。
  5. 写EOI之前,保持IRQ线和向量号的映射关系清晰。

有意思的是,在现代Linux启动早期,CPU会先用8259A方式响应中断,在切换到APIC模式后再重设IOAPIC。这个切换过程要求时序严格正确,否则早期键盘中断或者时钟中断会丢失。我第一次在内核启动早期调试串口时遇到的中断不触发问题,最后查出来就是CGA/串口的中断还在走老PIC中断线,而PIC已处于屏蔽状态,两个控制器之间没有做好overlay。

7. 一条主线上的三套具体方案对比:中断延迟与嵌套控制

最后我们从整体视角再看一眼这条主线上三个架构的核心权衡,顺便聊一下几件容易被忽略的事情。

7.1 中断延迟的构成差异

中断延迟通常指的是从外设触发中断到CPU开始执行第一条中断服务指令的时间。这个时间主要由两部分构成:中断控制器的仲裁时间+CPU的响应时间。

  • x86走的是高度硬件化的路线。APIC在硬件上可以完成目标CPU的选择和向量生成,CPU响应也是自动压栈跳转。这让它的中断延迟下限可以做得很低,但代价是硬件复杂度高。
  • ARM在GIC上有一个比较复杂的优先级和抢占模型,能做到中断嵌套的硬件控制,延迟取决于GIC的时钟和优先级仲裁策略。GIC的抢占机制是通过CPU Interface的Running Priority实现的——高优先级中断可以抢占低优先级中断正在执行的场景,但这个能力需要在初始化时明确配置。
  • RISC-V因为极简,PLIC本身不处理抢占,需要软件配合去实现嵌套控制。裸机环境下RISC-V的中断延迟可以非常短,但代码逻辑上必须保证中断服务程序能快速完成claim,否则PLIC的仲裁效率会浪费。

7.2 嵌套中断的三个架构答案

嵌套中断,也就是中断里再打断一个中断,是实际工程里绕不开的话题。

x86的经典答案是:Interrupt Gate自动清IF,所以默认不嵌套,要想支持嵌套得在中断服务程序里自己开IF。这个设计很保守,但容易理解。

ARM的GIC则提供了硬件级的优先级抢占支持。高优先级中断可以直接抢占当前正在执行的低优先级中断服务程序——只要GICC_CTLR的EnableGrp1配置符合预期。硬件自动完成上下文的压栈/出栈,但对软件要求更高:每个中断服务程序都必须正确地保存和恢复自己的上下文,否则嵌套之后现场就毁了。

RISC-V的PLIC本身不提供抢占,它只负责仲裁哪一个中断优先给CPU,至于CPU是不是在中断里,PLIC不关心。所以RISC-V要支持嵌套得这么干:中断入口先关全局中断,保存上下文,然后根据优先级决定是否重新使能全局中断,允许高优先级中断打入。这个逻辑全靠自己控制,写成通用框架时要非常小心。

7.3 实际调试中的经验清单

最后分享几个我踩过的、具有广谱参考价值的坑:

  1. 中断服务程序中不能随意调用printf或任何可能触发系统调用的函数。在RISC-V和ARM的裸机环境里,这类函数内部可能会开关中断或者调用锁,容易造成死锁或时序错乱。调试中断内的日志输出,建议用一个环形缓冲区先记录,回到主循环再统一输出。

  2. 多核系统的IPI(核间中断)数据一致性必须重视内存屏障。不只是ARM有这个问题,RISC-V的多核也一样。CPU核A写数据后触发IPI,CPU核B收到IPI去读数据——如果没有屏障,B可能读到旧值。这在三种架构上排查方式不同,但根因都一样:内存序。

  3. 电平触发和边沿触发的中断在清除标志时行为完全不一样。电平触发中断必须清除外设硬件的中断标志,否则中断线一直有效,中断服务程序返回之后立刻再次进入;边沿触发中断则要防止漏掉被屏蔽期间到达的边沿脉冲。ARM和x86的中断控制器都支持这两种触发方式,RISC-V的PLIC里有的版本也能够配置,但很多SoC直接固定成电平触发。设计驱动的时候要提前确认好。

  4. 中断返回之前的最后一步操作一定要严格跟架构对应。ARM的ERET、x86的IRETQ、RISC-V的MRET,这三条指令对处理器状态的恢复范围各不相同,误用了任何一种都会产生难以排查的故障。

有一条主线做骨架,三种架构的中断流程就变成了一套方法论在不同硬件上的具体落地。希望这篇文章能帮大家把知识串起来,以后遇到新架构的中断相关问题,心里先画这条五步线,排查起来思路就清晰很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询