|
[导读] 对于驱动开发而言,中断机制是一个无法绕开的主题,翻看了很多资料书籍,读来读去总觉得没明白,所以尝试自底向上的分析一下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! ^! K t) q. t5 K0 b .long __invalid_entry @ 2 - NMI- F3 p0 {+ q5 B! [3 v0 ] .long __invalid_entry @ 3 - HardFault .long __invalid_entry @ 4 - MemManage7 ?1 }5 e# b( y! T/ t( M .long __invalid_entry @ 5 - BusFault: Y' w/ h6 M8 T4 L; W: h6 m* S .long __invalid_entry @ 6 - UsageFault .long __invalid_entry @ 7 - Reserved .long __invalid_entry @ 8 - Reserved .long __invalid_entry @ 9 - Reserved r$ H) F2 R9 ~; q. ^ .long __invalid_entry @ 10 - Reserved .long vector_swi @ 11 - SVCall; r) l" H# S9 _- J8 t9 q9 | .long __invalid_entry @ 12 - Debug Monitor .long __invalid_entry @ 13 - Reserved; r& f+ d2 z# k: u .long __pendsv_entry @ 14 - PendSV .long __invalid_entry @ 15 - SysTick3 D, E! D9 D9 I0 z; a: T* Q" F. {, J .rept CONFIG_CPU_V7M_NUM_IRQ7 S% t) M8 R1 r. K5 }8 H .long __irq_entry @ External Interrupts .endr .align 2. _+ S0 V- I+ \: I( X J .globl exc_ret; C# j1 L, T! X exc_ret:$ f+ p! l( V9 G! ^# ^) w8 Y, c .space 4 对于其他的ARM架构而言,位于./arch/arm/kernel/entry-armv.S,贴上部分代码 ** Interrupt dispatcher4 S; L0 r5 t3 l2 X! w* S9 u4 w */ vector_stub irq, IRQ_MODE, 4 ! \+ E/ n @1 j% w4 Q5 s7 |/ u( H .long __irq_usr @ 0 (USR_26 / USR_32)- p5 v# e m3 {: @% R" X0 q, G# b. [ .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)& z( |( Q g3 _9 S .long __irq_svc @ 3 (SVC_26 / SVC_32)' P4 \& b; ?! { .long __irq_invalid @ 4, j1 Q8 v( K: x5 b! \ .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8+ d+ l2 T5 H! q! L8 P. J2 Q$ e .long __irq_invalid @ 97 J4 O" U. S) }* z- f0 O .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c. Y$ t6 [4 X0 j# O' y .long __irq_invalid @ d .long __irq_invalid @ e9 q1 b8 @# T1 X. m1 T# K .long __irq_invalid @ f + K. ]" n5 u) d& s/ I: c /* * Data abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */. k+ G9 H. G* h7 ^. J# u v0 T vector_stub dabt, ABT_MODE, 8 .long __dabt_usr @ 0 (USR_26 / USR_32); m9 m. F6 J& H2 O/ Y .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32): Z8 ]- i1 a* ?9 a f .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __dabt_svc @ 3 (SVC_26 / SVC_32)! w: O7 B. D- t* `/ s5 n5 x4 E .long __dabt_invalid @ 4 .long __dabt_invalid @ 53 I* w" b$ W7 { .long __dabt_invalid @ 6 .long __dabt_invalid @ 75 i( z; d z, l3 q! K4 a# W7 G .long __dabt_invalid @ 8 .long __dabt_invalid @ 9" h8 B" K; z0 l& m' B .long __dabt_invalid @ a .long __dabt_invalid @ b+ O+ m' n6 }% v1 t! D3 F, f .long __dabt_invalid @ c6 l1 c8 e, M" r5 d .long __dabt_invalid @ d- _. K0 d4 O! ^' i* g: d .long __dabt_invalid @ e .long __dabt_invalid @ f , s. g; K( w9 Y /* * Prefetch abort dispatcher- Y+ e Y# u( N$ X * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */6 V x* T% S, ?7 n3 k7 i vector_stub pabt, ABT_MODE, 4% [$ k2 W8 m* z .long __pabt_usr @ 0 (USR_26 / USR_32)% k O" l, f3 R" X- x' t7 O .long __pabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __pabt_invalid @ 2 (IRQ_26 / IRQ_32)/ @7 J, N a! X" F .long __pabt_svc @ 3 (SVC_26 / SVC_32)% S; [! z( k4 t ~ .long __pabt_invalid @ 44 h" M" J! G# u5 P( i9 R3 U .long __pabt_invalid @ 5 .long __pabt_invalid @ 63 n7 G) \- z$ Z7 r7 C& F" B6 b .long __pabt_invalid @ 7 .long __pabt_invalid @ 86 V! c) [+ u2 k .long __pabt_invalid @ 9! o& f3 N5 W( @% h5 x1 a/ l+ c .long __pabt_invalid @ a7 ^, s# P" }! b: O3 [5 r1 h .long __pabt_invalid @ b1 W: m. p' a- x0 V' \ .long __pabt_invalid @ c- I- h2 D. Q8 \& ] .long __pabt_invalid @ d" m' Q' M% x+ w* {/ Y: P .long __pabt_invalid @ e' U! ]" M& v0 s) `- d .long __pabt_invalid @ f, S) E/ ]. ~' s% \0 [ % }1 x/ ]- C7 q5 } /*6 F- r" m$ v) r( G * Undef instr entry dispatcher * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC% L& v O2 Q; f) o% ]) ] */ vector_stub und, UND_MODE . ~( A2 `! o2 j% [ .long __und_usr @ 0 (USR_26 / USR_32)0 _& z E3 G& B( d .long __und_invalid @ 1 (FIQ_26 / FIQ_32) .long __und_invalid @ 2 (IRQ_26 / IRQ_32)% S! {6 ?1 f" F9 C( T1 ] .long __und_svc @ 3 (SVC_26 / SVC_32)3 p$ Y% `% m: A+ L- L .long __und_invalid @ 4: X3 {# o" q" `; e .long __und_invalid @ 5+ k5 r9 [6 O. W) d .long __und_invalid @ 6. @0 A& x. t) Y .long __und_invalid @ 72 [/ Y* U1 H2 r7 p0 L- K .long __und_invalid @ 8 .long __und_invalid @ 9 .long __und_invalid @ a .long __und_invalid @ b .long __und_invalid @ c1 ~% R" W9 ^$ j B: R .long __und_invalid @ d% c: P- A h" J- ]8 v# Z' L! b .long __und_invalid @ e( t: p7 b/ j4 T( {6 o/ v .long __und_invalid @ f: s* Y3 t: q4 V% E9 a" x$ t3 t .align 5 .....................* D1 t: `3 O' o% t /*============================================================================= * FIQ "NMI" handler *----------------------------------------------------------------------------- */) j6 n, c1 f$ _' [: a8 M vector_stub fiq, FIQ_MODE, 4 .long __fiq_usr @ 0 (USR_26 / USR_32)7 v$ p4 W0 B' U* w" t- f% x. [ .long __fiq_svc @ 1 (FIQ_26 / FIQ_32)% r5 @, ^/ v1 K+ O8 J .long __fiq_svc @ 2 (IRQ_26 / IRQ_32)" R1 l. j) u: t9 A( | .long __fiq_svc @ 3 (SVC_26 / SVC_32) .long __fiq_svc @ 4 .long __fiq_svc @ 5 e4 ^% R9 k [# X( J! v .long __fiq_svc @ 6 .long __fiq_abt @ 70 E6 h5 S1 }: p$ }) G. f .long __fiq_svc @ 8; A# `! O# R7 g9 d, Z .long __fiq_svc @ 9+ s, c. l M3 K' I .long __fiq_svc @ a* K2 b7 g& z: b .long __fiq_svc @ b .long __fiq_svc @ c' v- e. Q- H' B$ ^2 V2 J) p9 W# @, I9 o .long __fiq_svc @ d .long __fiq_svc @ e .long __fiq_svc @ f 7 g! O9 q, \5 h. ^ .globl vector_fiq .section .vectors, "ax", %progbits; o& R5 h% m& a+ L/ k q .L__vectors_start:! \2 A) r$ [; z0 { W(b) vector_rst W(b) vector_und W(ldr) pc, .L__vectors_start + 0x1000 W(b) vector_pabt) a0 P3 P% C& U' n. e W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq .data! K2 Q) p* O7 f .align 2 7 J# }' j/ L+ L% Z& R& O0 \ .globl cr_alignment, }$ |' E$ R/ v u/ m9 t5 r+ F/ r cr_alignment:- R. M% A, E' B( [6 a7 [) g .space 4 ARM64 位于./arch/arm64/kernel/entry.S,贴上部分代码 .pushsection ".entry.text", "ax"+ u, g1 u6 f& Y; V; B; {* W. I$ J; l G' K4 M7 c5 C; l .align 11 ENTRY(vectors)9 ?7 J1 a- K4 Q N, v0 ]9 y kernel_ventry 1, sync_invalid // Synchronous EL1t5 n: o1 f6 e1 e, [. \) [: j kernel_ventry 1, irq_invalid // IRQ EL1t6 |& [7 L$ u! P5 {0 c, g kernel_ventry 1, fiq_invalid // FIQ EL1t/ t |- y" k# u kernel_ventry 1, error_invalid // Error EL1t kernel_ventry 1, sync // Synchronous EL1h kernel_ventry 1, irq // IRQ EL1h1 R. i) D: e' ~* [) T kernel_ventry 1, fiq_invalid // FIQ EL1h% L. s8 c% c1 J kernel_ventry 1, error // Error EL1h- z5 V9 f1 Z( ?' Q: R# d9 p kernel_ventry 0, sync // Synchronous 64-bit EL03 [( x8 M! g' V8 K kernel_ventry 0, irq // IRQ 64-bit EL01 d- U2 E0 m7 ~- l h! i% c% s$ V kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 kernel_ventry 0, error // Error 64-bit EL0 1 n9 `2 `$ q5 E: e #ifdef CONFIG_COMPAT kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0' _% e% q6 H& D0 z1 U9 H kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL00 n8 M# O5 `; q kernel_ventry 0, error_compat, 32 // Error 32-bit EL03 \4 z3 h* |( ?9 U( a2 Z% l* Z- | #else! A" S" i* c# C7 S3 q) R( ?+ A* T kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL03 e" n. k8 i9 z kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL08 D, n& O+ u: |' s. C* v2 t kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0 ?( |2 P o0 z* d: f/ C1 X& z kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0$ v4 |) _0 L8 z& K0 m& U #endif3 h, d9 E9 Y9 A# o( W END(vectors) 总结一下 对于做嵌入式开发,尤其需要做底层驱动开发的小伙伴们,较深入的理解一下更为底层异常/中断运行的机制,对于具体驱动开发而言是非常有帮助的。本文参考内核代码,以及ARM官方规格书大致梳理了Linux中断子系统的基础概念,以及异常/中断如何进入到CPU,以及相应的入口在哪里实现定义的。后续为逐步深入分析中断底层如何建模抽象的,采用自底向上的分析策略进而分析用户空间的调用接口,敬请关注期待。 0 Y6 _+ S: v( ~1 n |
微信公众号
手机版