|
[导读] 对于驱动开发而言,中断机制是一个无法绕开的主题,翻看了很多资料书籍,读来读去总觉得没明白,所以尝试自底向上的分析一下Linux中断子系统的内在设计以及运行机制。将陆续分享相关的学习原创笔记,敬请关注期待。 代码分析基于内核5.4.31 啥是中断/异常处理器的典型任务是处理一系列预定的程序。为了通知处理器某些事件,有时需要中断当前正在处理的任务。中断可以由程序触发,也可以从外设异步触发。如果发生中断请求IRQ,则处理器将执行预定的中断服务程序ISR。CPU需要能处理软件错误,例如除零或显式中断调用(例如syscalls)。硬件中断由中断控制器路由到指定的处理器(如x86系统的IO-APIC)。 操作系统的总体运行机制属于事件驱动(event-driven)机制。 中断:
一个 IRQ 是来自某个设备的一个中断请求。可能的中断请求源:来自一个硬件引脚,或来自一个数据包。多个设备可能连接到同一个硬件引脚,从而共享一个 IRQ。抽象成中断请求事件,有哪些可能的事件呢:
异常(陷阱):由软件生成的同步事件,比如前面提到的除零,页错误 ARM32/ARM64硬件架构对比ARM32这里以ARMv7 为例进行描述,对于ARM32而言,CPU具有9大处理模式:
其中encoding是指的CPSR程序状态寄存器中的M[4:0],对于ARMv7而言,权限等级如下:
ARMv7采用通用中断控制器GIC V2进行中断分发控制。 AArch64自《Programmer’s Guide for ARMv8-A》,armv8 引入64位ARM架构,向后兼容。
ARMv8-A体系结构引入了许多更改,这些更改使得可以设计性能明显更高的处理器实现:
上面谈到了新的异常模型,这里来看一下具体是指什么: 在ARMv8中,程序运行总是处于四个异常级别之一。在AArch64中,异常级别确定特权级别,类似于ARMv7中定义的特权级别。异常级别确定特权级别,因此在ELn程序对应于特权PLn。类似地,n值大于另一个值的异常级别处于较高的异常级别。数量比另一个少的异常级别被描述为处于较低的异常级别。 异常级别提供了适用于ARMv8架构所有运行状态的软件执行特权的逻辑隔离。它类似于计算机科学中常见的分层保护域概念。
通常,一个软件(例如应用程序,操作系统的内核或系统管理程序)占据一个异常级别。该规则的一个例外是内核内虚拟机管理程序,例如KVM,它们在EL2和EL1上都可运行。 可运行在AArch32以及AArch64模式下:
由图可见:在AArch32模式下,EL0相对于usr 模式,而EL1则相当于Svc、Abt、Und、FIQ、IRQ、Sys模式,而EL2则相当于Hyp模式。 ARMv8-A提供两种安全状态,即安全和非安全。非安全状态也称为正常模式(normal world)。这使操作系统(OS)与受信任的OS在同一硬件上并行运行,并提供了针对某些软件攻击和硬件攻击的保护。ARM TrustZone技术使系统可以在普通和安全环境之间进行分区。与ARMv7-A架构一样,安全监视器充当在普通和安全环境之间移动的网关。 ARMv8-A 在正常模式下还提供了对虚拟化的支持。从而虚拟机监控程序或虚拟机管理器(VMM)代码可以在系统上运行,并承载多个客户操作系统。每个客户操作系统本质上都运行在一个虚拟机上。每个操作系统就不会意识到它正在与其他客户操作系统共享CPU。 ARMv8-A采用通用中断控制器GIC V3进行中断分发控制。 啥是GIC? GIC是一种先进的微控制器总线架构(AMBA)和ARM架构兼容的系统片上(SoC)外设。它是一种高性能的、区域优化的中断控制器,具有芯片上的AMBA总线接口,根据配置,它符合AMBA高级可扩展接口(AXI)协议或AMBA AHB-Lite协议。 这里仅仅参考ARM官方文档将其中一部分从概念上加以介绍,过多细节比较枯燥就不做介绍了,个人认为仅需要从概念上去理解一下大致原理即可,没有必要去进行更深层的挖掘。除非你需要去修改这一层次的代码。
具体一点来看,GIC的总体框架如下:
比如一个SPI的中断分发路由机制如下:
分发器 Distributor:分发服务器执行中断优先级排序,并将spi和SGIs分发到连接到系统中PEs,从而进入CPU处理。如果我们将这些都看成黑盒子,可以简化理解一下:
对于GICv2与GICv3的主要显著区别是GICv3可以支持更多核,GICv3可支持超过8核的处理器。 异常/中断来了咋办?
然而对于异常/中断的处理,说到底还是需要进入相应的异常/中断句柄(process handler)进行处理,那么怎么进入的呢?这里自然就引入了异常向量表了,这里来看看异常向量表在哪里实现的。这里个人认为理解一下大致概念也就可以了。 ARM对于ARMv7-M而言,,位于./arch/arm/kernel/entry-v7m.s ENTRY(vector_table).long 0 @ 0 - Reset stack pointer .long __invalid_entry @ 1 - Reset .long __invalid_entry @ 2 - NMI .long __invalid_entry @ 3 - HardFault .long __invalid_entry @ 4 - MemManage .long __invalid_entry @ 5 - BusFault .long __invalid_entry @ 6 - UsageFault .long __invalid_entry @ 7 - Reserved .long __invalid_entry @ 8 - Reserved .long __invalid_entry @ 9 - Reserved .long __invalid_entry @ 10 - Reserved .long vector_swi @ 11 - SVCall .long __invalid_entry @ 12 - Debug Monitor .long __invalid_entry @ 13 - Reserved .long __pendsv_entry @ 14 - PendSV .long __invalid_entry @ 15 - SysTick .rept CONFIG_CPU_V7M_NUM_IRQ .long __irq_entry @ External Interrupts .endr .align 2 .globl exc_ret exc_ret: .space 4 对于其他的ARM架构而言,位于./arch/arm/kernel/entry-armv.S,贴上部分代码 ** Interrupt dispatcher */ vector_stub irq, IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f /* * Data abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */ vector_stub dabt, ABT_MODE, 8 .long __dabt_usr @ 0 (USR_26 / USR_32) .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __dabt_svc @ 3 (SVC_26 / SVC_32) .long __dabt_invalid @ 4 .long __dabt_invalid @ 5 .long __dabt_invalid @ 6 .long __dabt_invalid @ 7 .long __dabt_invalid @ 8 .long __dabt_invalid @ 9 .long __dabt_invalid @ a .long __dabt_invalid @ b .long __dabt_invalid @ c .long __dabt_invalid @ d .long __dabt_invalid @ e .long __dabt_invalid @ f /* * Prefetch abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */ vector_stub pabt, ABT_MODE, 4 .long __pabt_usr @ 0 (USR_26 / USR_32) .long __pabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __pabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __pabt_svc @ 3 (SVC_26 / SVC_32) .long __pabt_invalid @ 4 .long __pabt_invalid @ 5 .long __pabt_invalid @ 6 .long __pabt_invalid @ 7 .long __pabt_invalid @ 8 .long __pabt_invalid @ 9 .long __pabt_invalid @ a .long __pabt_invalid @ b .long __pabt_invalid @ c .long __pabt_invalid @ d .long __pabt_invalid @ e .long __pabt_invalid @ f /* * Undef instr entry dispatcher * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC */ vector_stub und, UND_MODE .long __und_usr @ 0 (USR_26 / USR_32) .long __und_invalid @ 1 (FIQ_26 / FIQ_32) .long __und_invalid @ 2 (IRQ_26 / IRQ_32) .long __und_svc @ 3 (SVC_26 / SVC_32) .long __und_invalid @ 4 .long __und_invalid @ 5 .long __und_invalid @ 6 .long __und_invalid @ 7 .long __und_invalid @ 8 .long __und_invalid @ 9 .long __und_invalid @ a .long __und_invalid @ b .long __und_invalid @ c .long __und_invalid @ d .long __und_invalid @ e .long __und_invalid @ f .align 5 ..................... /*============================================================================= * FIQ "NMI" handler *----------------------------------------------------------------------------- */ vector_stub fiq, FIQ_MODE, 4 .long __fiq_usr @ 0 (USR_26 / USR_32) .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) .long __fiq_svc @ 3 (SVC_26 / SVC_32) .long __fiq_svc @ 4 .long __fiq_svc @ 5 .long __fiq_svc @ 6 .long __fiq_abt @ 7 .long __fiq_svc @ 8 .long __fiq_svc @ 9 .long __fiq_svc @ a .long __fiq_svc @ b .long __fiq_svc @ c .long __fiq_svc @ d .long __fiq_svc @ e .long __fiq_svc @ f .globl vector_fiq .section .vectors, "ax", %progbits .L__vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, .L__vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq .data .align 2 .globl cr_alignment cr_alignment: .space 4 ARM64 位于./arch/arm64/kernel/entry.S,贴上部分代码 .pushsection ".entry.text", "ax".align 11 ENTRY(vectors) kernel_ventry 1, sync_invalid // Synchronous EL1t kernel_ventry 1, irq_invalid // IRQ EL1t kernel_ventry 1, fiq_invalid // FIQ EL1t kernel_ventry 1, error_invalid // Error EL1t kernel_ventry 1, sync // Synchronous EL1h kernel_ventry 1, irq // IRQ EL1h kernel_ventry 1, fiq_invalid // FIQ EL1h kernel_ventry 1, error // Error EL1h kernel_ventry 0, sync // Synchronous 64-bit EL0 kernel_ventry 0, irq // IRQ 64-bit EL0 kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 kernel_ventry 0, error // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_compat, 32 // Error 32-bit EL0 #else kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0 #endif END(vectors) 总结一下 对于做嵌入式开发,尤其需要做底层驱动开发的小伙伴们,较深入的理解一下更为底层异常/中断运行的机制,对于具体驱动开发而言是非常有帮助的。本文参考内核代码,以及ARM官方规格书大致梳理了Linux中断子系统的基础概念,以及异常/中断如何进入到CPU,以及相应的入口在哪里实现定义的。后续为逐步深入分析中断底层如何建模抽象的,采用自底向上的分析策略进而分析用户空间的调用接口,敬请关注期待。 |
微信公众号
手机版