当前位置: 首页 > news >正文

Arm Linux中断溯源(一)

Linux中断相比单片机实在是复杂了太多,但本质原理是相通的,这里写文章记录一下学习中的一些思考,后面想起来再继续补充。文章主要基于《图解Linux内核》、《Linux内核设计与实现》这两本书,由于它们都是基于x86进行讲解的,但我需要从事ARM Linux嵌入式开发,所以遇到与x86的不同之处,选择教程比较丰富的I.MX6ULL和STM32MP157内核进行比较。写这篇文章也是为了给自己学习东西的动力,毕竟不懂的东西就说不清,也不能乱说,写上来也就说明我应该是明白了。本篇部分内容是和AI“讨论”出来的,如有错误欢迎指正。

引言

无论使用何种架构,中断处理永远可以概括为三步:

  • 保存现场->跳转执行中断处理函数->恢复现场

其中跳转执行中断处理函数是与开发者关系最密切的事情,而保存现场、恢复现场都是必不可少的流程,并且在Linux内核中有很大篇幅。一般开发者使用中断,就是向内核在某条中断线上注册触发时的处理函数,为了知道能在中断处理函数做什么、不能做什么,就必须理解中断上下文。而Linux中从中断触发到执行中断处理函数的调用关系非常复杂,无疑为理解中断上下文制造了麻烦,这也就是为什么要去溯源。

我学习中断还有一个目标,就是弄清楚Linux内核到底是怎么做到适配那么多架构的,学习它的编程思想。

异常与中断

这两个名词都是形容打断当前执行流(PC不断+1的正常流程),跳转执行新的执行流的行为,但根据产生的原因,可以分为:

  • 中断
    是指来自CPU之外的硬件设备,打断CPU的执行流,是异步(无法预知在执行哪个指令时会到达)的
  • 异常
    是指来自CPU内部,由当前正在执行的指令导致的,是同步(可以预见,只要执行这条指令必然会产生)的

还有个名词,IRQ(Interrupt Request),说的就是中断,而不是异常

Cortex-M3中断原理

先用单片机热热身,虽然与Linux处理方式很不同,但是原理是共通的

Cortex-M3支持11个异常和最多240个外部中断,如下表,摘自《Cortex-M3权威指南》,这节后面的图也一样,搞单片机的肯定得知道这些

这里说的中断,就是中断CPU执行流的行为,而不仅仅是外部中断、IRQ了,注意区分

  • 保存什么现场?保存在哪里?
    硬件会自动保存一部分寄存器:硬件会把8个寄存器地址入栈(较真的话,Cortex-M3有2组栈指针,MSP(主栈)和PSP(进程栈),此时硬件会就近保存到正在使用的那个栈里),分别为xPSR、PC、LR、R12、R3-R0,并且严格的说,这个顺序是入栈后、从高到底的栈地址里面的、值的意义,并不是时间上的入栈的顺序:

时间上的压栈顺序是有讲究的,是优化过的。有个疑问,为什么图里的地址(HADDR)和数据(HWDATA)是错开的?这个是总线流水线:在第1个周期,地址N-8被发出去;第2个周期,地址N-4被发出去,同时N-8对应的数据"PC"被送到数据总线上;第3个周期…流水线可以保证每个周期都在推进压栈流程,缩短中断响应时间。

  • CPU状态有什么变化?
    Cortex-M3有2种操作模式和2个特权等级
    操作模式:处理者模式(Handler mode)和线程模式(Thread mode)
    特权等级:特权级(Privileged)和用户级(Unprivileged或User)

运行在Thread mode时,既可以是Privileged,也可以是User。运行在Handler mode时,必定是Privileged。操作模式和特权等级的状态转移如下图:

初学单片机,大家都从裸机开始,没有手动切换过特权等级,从上电复位之后,一直都运行在特权级线程模式。而一旦发生异常,CPU处理中断期间,是在特权级handler模式下进行的(可以理解为处理中断时拥有最高权限,所以中断确实要小心编程)。事实上,裸机编程,状态就在图里上面两个圈之间转换,使用的栈是唯一的MSP。当加入RTOS后,图里3个状态都会存在了,也开始使用PSP。RTOS下的CPU状态转换这里就不再赘述,以后可能会写一篇FreeRTOS的学习笔记。只需要关注,发生中断行为时,CPU的状态确实发生了变化,特权等级会提升就好。

  • 中断上下文的函数用的什么栈?
    甭管发生中断前一刻、被打断的现场用的什么栈,进入中断上下文后,一律用的是MSP(说的是Cortex M3,其他架构后面再细究)。

  • 既然被打断,跳转去执行新的代码,那跳到哪?
    对于ARM架构,就是跳转到异常向量表(中断向量表)。说是个表,其实是一段区域,里面记录的是发生异常时,应该执行的函数,排列整齐,像个表

  • 1个表项只有4个字节的大小,这里放的是什么东西?
    确实放不了什么东西,只有1条跳转指令,正好4个字节

  • 为什么是用偏移量0x04表示,而不是真正的绝对地址,如内存中的0x00000004?
    因为中断向量表所处的区域是可以自定义的,有个寄存器叫VTOR(向量表偏移寄存器),可以控制向量表整体偏移

  • 这个表里面的东西,对应的是跳转到某个函数的指令对吧,那在何时放置的这些指令?
    在startup.s里被指定,参与编译、链接成二进制文件后,由烧录器将指令、指令所跳转的函数下载到正确的地址,有关startup.s这里就不写了,网上很多,可以自己找一份丢给AI,打打注释,研究研究

Cortex-A7中断原理

Cortex-A7相比Cortex M3已经复杂了很多,这里引用正点原子《I.MX6U嵌入式Linux驱动开发指南》,虽然他们也是引用的其他手册,但整理在一起还是更方便一些

还记得Cortex M3的2种模式吗?Thread mode和Handler mode,Cortex A7有9种工作模式,除了USR用户模式外,其他全是特权模式。和Cortex M3类似的,用户模式想要切换到其他模式,就只能触发异常。Cortex M3只有SP(R13)寄存器在2种模式下有不同的备份(MSP和PSP),但Cortex A7有很多寄存器组,在不同的模式下都有不同的备份。下面图里,蓝绿色的就是各个模式独有的寄存器,灰色的是所有模式公共的寄存器。

  • CPU发生的模式转变
    毫无疑问的,中断发生时,CPU会切换到IRQ模式,就像Cortex M3里面切换到handler模式一样。查看CPSR寄存器(程序状态寄存器),就可以知道CPU现在处于什么模式。

  • 保存的现场
    Cortex A7完全依靠软件压栈,这点和Cortex M3完全不一样,它的现场是没有硬件去自动保存的。因此具体做了什么,还需要去溯源Linux内核代码。

  • 跳转的向量表
    Cortex A7只有8个异常向量,没错只有8个,Cortex M3可是有11个异常+至多240个IRQ,怎么性能越强反而越少了?其实是越来越高级了,这里面一个IRQ Interrupt就把上百个IRQ全都包含了。具体怎么细分的到下一章再写吧。

对应的汇编入口:

I.MX6ULL内核 arch/arm/kernel/entry-armv.S /* * 定义一个名为 .vectors 的段 * "ax" = 可分配(allocate) + 可执行(executable),是代码段 * %progbits = 段里存真实机器码,不是未初始化数据 */ .section .vectors, "ax", %progbits /* * __vectors_start: * 这就是 **i.MX6ULL Linux 的异常向量表基地址** * 共 8 个异常向量,每个占 4 字节,对应 ARM 标准顺序: * 0: 复位 * 1: 未定义指令 * 2: SWI/SVC(软中断) * 3: 预取中止 * 4: 数据中止 * 5: 保留 * 6: IRQ(通用中断) * 7: FIQ */ __vectors_start: W(b) vector_rst @ 0x00 复位异常 W(b) vector_und @ 0x04 未定义指令异常 W(ldr) pc, __vectors_start + 0x1000 @ 0x08 SVC/SWI 软中断(**特殊!**) W(b) vector_pabt @ 0x0c 预取中止 W(b) vector_dabt @ 0x10 数据中止 W(b) vector_addrexcptn @ 0x14 保留(地址异常) W(b) vector_irq @ 0x18 IRQ 中断 W(b) vector_fiq @ 0x1c FIQ 快速中断 /* W (x) = 让指令同时支持 ARM/Thumb 模式,内核通用宏。 W(b)和W(ldr)就是通用的b和ldr的意思 那为什么SVC/SWI异常不使用W(b) vector_svc? 因为SVC Linux 给它单独做了一张表 */

以下就是定义vector##xxx的宏,组合成一个名字
这意味着,这些vector_xxx所做的东西其实高度重复:

I.MX6ULL内核 arch/arm/kernel/entry-armv.S /* * Vector stubs. * 向量桩(异常/中断的底层入口) * * This code is copied to 0xffff1000 so we can use branches in the * vectors, rather than ldr's. Note that this code must not exceed * a page size. * 这段代码会被复制到内存 0xffff1000 地址 * 目的是让向量表能用简单的跳转指令(B),而不是复杂加载(LDR) * 注意:代码大小不能超过一页内存 * 原因:B指令是相对跳转,只能跳转到当前范围的一页内,比LDR更快更小 * * Common stub entry macro: * 通用的异常入口宏定义 * Enter in IRQ mode, spsr = 发生异常前的 CPSR * lr = 发生异常前的 PC * 这里说的SPSR其实就是正点原子那张图里,CPSR的备份。切换模式前的CPSR被暂存在SPSR内 * 返回时可以恢复到CPSR。同样还有LR、SP指的是LR_IRQ、SP_IRQ,而不是USER里的那一套 * * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. * SP(SP_IRQ) 指向一小块 CPU 私有内存 * 这块地址会传给 r0,供异常处理函数使用 * 人话:把异常发生时的硬件现场指针SP_IRQ,当作C 语言函数的第一个参数 * 对应mov r0, sp * 这有伏笔,意味着在进入C函数前,SP_IRQ里面首先肯定需要保存、构造什么东西 */ // 宏定义:vector_stub 名字,模式,修正值(默认0) .macro vector_stub, name, mode, correction=0 .align 5 @ 按 32 字节对齐(ARM 要求异常向量对齐) vector_\name: @ 拼接成函数名,例如 vector_irq .if \correction sub lr, lr, #\correction @ 如果需要修正,把返回地址减修正值 @ **因为异常时PC会超前,必须修正才能正确返回** .endif @ ============================== @ 第一步:保存最关键的现场,这一步就是在构造栈 SP_IRQ @ ============================== @ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ @ 保存 r0(通用寄存器)+ lr(异常发生前的PC) stmia sp, {r0, lr} @ 把 spsr(异常前的 CPSR 状态)读到 lr mrs lr, spsr @ 把 spsr 存到栈的 [sp+8] 位置 str lr, [sp, #8] @ ============================== @ 第二步:准备切回 SVC 模式(内核态) @ @ Prepare for SVC32 mode. IRQs remain disabled. @ 进入 IRQ 中断的瞬间,ARM 硬件会自动、强制、无条件关闭 IRQ! @ 从进入中断到现在,IRQ 一直被硬件关着,从来没打开过 @ 所以任何IRQ在此时都无法嵌套 @ /* 补充:Linux 内核所有 C 语言代码,只跑在 SVC 模式!为了栈和统一的内核上下文 IRQ 模式的栈很小、很特殊,不能跑 C 语言! Linux 设计时就定死了: 用户态 = USR 模式 内核态 = SVC 模式 IRQ / FIQ / ABT 等模式 = 只用来进一下门,立刻切走! 因为 SVC 模式有内核的主栈,很大、很安全、C 语言能正常跑。 */ @ ============================== @ 读取当前 CPSR 状态 → r0 mrs r0, cpsr @ 计算新的 CPSR:切换到 SVC 模式 eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) @ 把新状态写入 spsr msr spsr_cxsf, r0 //很重要!这里写的是SPSR,而不是CPSR! //写的是备份寄存器,不影响CPSR,仍然是IRQ模式! //直到movs pc, lr ,会自动从SPSR中恢复到CPSR,自动完成模式切换 @ ============================== @ 第三步:根据异常类型跳转到真正的C处理函数,这时仍是IRQ模式 @ ============================== @ 从 spsr 里取出低4位(异常类型)→ lr @ @ the branch table must immediately follow this code @ 跳转表必须在这段代码之后(这个宏结束)!伏笔! @ 宏后面必须跟跳转表! and lr, lr, #0x0f THUMB( adr r0, 1f ) THUMB( ldr lr, [r0, lr, lsl #2] ) @ 把栈指针传给 r0(给C函数用) //伏笔回收,SP_IRQ里确实塞了参数,毫无疑问这就是保存的现场 mov r0, sp @ ARM 模式:查表,根据异常类型找到处理函数地址 → lr //人话:把 内存地址 PC + (LR 左移2位) 里的值,读出来,放到 LR 中 //lr = *( pc + (lr << 2) ) //到这里时,lr=SPSR的低4位,代表中断前发生的CPU工作模式 // ARM( ldr lr, [pc, lr, lsl #2] ) @ 跳转并切换CPU状态到 SVC 模式 @ **真正跳转到 C 语言的中断/异常处理函数** movs pc, lr ENDPROC(vector_\name) @ 宏结束

代码里面还有一句话:the branch table must immediately follow this code
所以紧跟着:

I.MX6ULL内核 arch/arm/kernel/entry-armv.S 伏笔回收,5张跳转表: /* * 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 ... /*============================================================================= * FIQ "NMI" handler *----------------------------------------------------------------------------- * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 * systems. */ 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

现在疑问越来越多了!一条一条解决!

  • 发生中断时,这部分汇编的跳转流程?
    异常向量表->向量桩(vector_stub)->发生中断前对应模式的跳转表
    比如__irq_usr,意思是IRQ中断打断了原来的USR模式,那最终就跳到这里

  • 这部分入口代码用的栈是什么?
    是SP_IRQ,一个独立的栈,中断的现场不是像Cortex M3那样、硬件就近保存到MSP/PSP的,而是需要vector_stub的代码软件保存在SP_IRQ中

  • 汇编宏怎么看的?
    和内联函数(inline)一样原地展开,比如下面这个调用:
    vector_stub irq, IRQ_MODE, 4
    就带入到vector_stub定义中,在这里原地展开,这也就是为什么它的后面紧跟跳转表,因为PC确实是在一条一条+1的,执行到汇编宏最后的指令:

//lr = *( pc + (lr << 2) ) ARM( ldr lr, [pc, lr, lsl #2] ) @ **真正跳转到 C 语言的中断/异常处理函数** movs pc, lr

这个时候,pc就是指向紧随其后的那个跳转表表头,lr是从中断前的spsr取出的低4位,代表异常类型,两个一相加,正好指向vector_stub irq, IRQ_MODE, 4宏后面紧跟的跳转表地址,完美

  • 这个过程中的CPU模式切换?
    重复一下,这个过程是指异常向量表->向量桩(vector_stub)->由当前模式和打断的模式细分的、下划线开头的中断处理函数

我们只讨论IRQ中断的情况,发生IRQ的那一刻,毫无疑问的,不管之前是什么模式,此刻CPU切换到了IRQ模式,跳转到向量桩:

vector_stub irq, IRQ_MODE, 4(汇编宏,需要展开)

vector_stub内部给spsr赋了SVC模式的值,但SPSR不是CPSR,直到最后跳转到__irq_xxx函数前,毫无疑问的仍然处于IRQ模式

直到最后,调用movs pc, lr,硬件会恢复SPSR中的值到全局CPSR,那这下不得不切换到SVC模式了!也就是说,__irq_xxx函数是在SVC模式执行的。也好理解,因为内核代码都是在SVC模式执行的。

总之:触发IRQ中断、跳到异常向量表、执行向量桩(vector_stub),这段时间都是IRQ模式,直到调用__irq_xxx内核函数,就自动切换成了SVC模式!

  • 这一过程CPSR、SPSR发生的变化?

发生IRQ中断,一瞬间CPSR(假设为USR)就拷贝到了SPSR_IRQ,之后CPSR变成IRQ模式
此时:CPSR = IRQ,SPSR_IRQ = USR

vector_stub里面,会把SPSR_IRQ先存到栈里,用USR模式去计算__irq_xxx跳转表的偏移,最终精确的调用__irq_usr。那SPSR_IRQ本体也没闲着,存到栈里之后,vector_stub会给SPSR_IRQ赋值SVC,此时SPSR_IRQ = SVC,CPSR = IRQ

最后,movs pc, lr,将SPSR_IRQ恢复到CPSR,那当然CPSR = SVC了
之后,执行__irq_usr时,CPSR = SVC

  • Cortex M3有抢占的规则,异常是可以嵌套的,那这段代码会被嵌套吗?
    不会,当发生IRQ的时候,CPSR.I = 1(硬件自动关IRQ),直到跳入__irq_xxx,IRQ都没有被重新打开,这就意味着这段代码根本不会发生嵌套。原因我想也很好解释,因为中断嵌套会导致栈爆发,这段代码是在SP_IRQ中保存的,本来就小,中断嵌套必然会导致SP_IRQ爆炸。尤其是Linux,不把进来时的SP_IRQ带走,就别想再往里面写新东西。(见勘误)

  • 那最后SP_IRQ出栈(销毁)了吗?
    没有,因为后面还用它作为参数R0,传入__irq_xxx函数了,这里面的东西仍然有用(见勘误)

  • 为什么 vector_stub irq, IRQ_MODE, 4 的跳转向量里面,除了__irq_usr和__irq_svc,其他全都是__irq_invalid?
    感觉是很好的问题,首先,选择进入__irq_usr,还是进入__irq_svc,是根据IRQ中断打断的现场(CPSR)决定的,既然只有__irq_usr和__irq_svc,那就说明,IRQ能打断的模式,必然只可能是USR和SVC!更进一步的,Linux用户程序永远跑在USR,内核代码永远跑在SVC!更更进一步的,其实那么多异常,很多Linux内核都没怎么实现,只要搞懂IRQ和SVC就撑起了整个用户态 、内核态 、硬件的全部关系

  • __vectors_start入口中,除了SVC/SWI异常是用W(ldr)到一个固定地址,其他都是W(b)到一个vector_stub,为什么它画风不一样?
    因为vector_swi在另一个文件中,不在同一个page,b指令跳不过去。精力有限,实在不想分析了,我写这个是为了研究中断,不是系统调用,中断我编驱动可能还用得到,但系统调用可能这辈子都不会修改了,所以这些以后再研究吧:

/arch/arm/kernel/entry-common.S /*============================================================================= * IMX6ULL (Cortex-A7 ARMv7-A) 专属 SWI/SVC 系统调用入口 * 功能:用户态 -> 内核态 系统调用总入口(open/read/write 等) *----------------------------------------------------------------------------- */ .align 5 ENTRY(vector_swi) @ ============================== @ 1. 保存用户态所有寄存器到内核栈 (IMX6ULL 硬件上下文) @ ============================== sub sp, sp, #S_FRAME_SIZE @ 开辟栈空间,存储全部寄存器 stmia sp, {r0 - r12} @ 保存 r0~r12 通用寄存器 add r8, sp, #S_PC stmdb r8, {sp, lr}^ @ 保存用户态 SP、LR mrs r8, spsr @ 读取 SPSR (中断前的CPSR) str lr, [sp, #S_PC] @ 保存用户态 PC str r8, [sp, #S_PSR] @ 保存用户态 CPSR str r0, [sp, #S_OLD_R0] @ 保存原始 r0 @ ============================== @ 2. IMX6ULL EABI 标准:系统调用号存在 r7 中 @ ============================== @ EABI 规则:r7 = 系统调用号 (无需读取指令,直接用r7) @ ============================== @ 3. 初始化内核环境 @ ============================== zero_fp @ 清空帧指针 alignment_trap r10, ip, __cr_alignment enable_irq @ 开启中断(内核态允许中断) ct_user_exit get_thread_info tsk @ 获取当前进程信息 @ ============================== @ 4. 加载系统调用表基地址 @ ============================== adr tbl, sys_call_table @ 指向 IMX6ULL 内核系统调用表 @ ============================== @ 5. 检查系统调用跟踪(调试用) @ ============================== local_restart: ldr r10, [tsk, #TI_FLAGS] stmdb sp!, {r4, r5} tst r10, #_TIF_SYSCALL_WORK bne __sys_trace @ 进入跟踪流程(调试) @ ============================== @ 6. 校验调用号 + 执行系统调用 @ ============================== cmp scno, #NR_syscalls @ 检查系统调用号是否合法 adr lr, BSYM(ret_fast_syscall) @ 系统调用完成后的返回地址 ldrcc pc, [tbl, scno, lsl #2] @ 【核心】跳转到对应 sys_xxx 函数 @ ============================== @ 7. 非法系统调用处理 @ ============================== add r1, sp, #S_OFF cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE) eor r0, scno, #__NR_SYSCALL_BASE bcs arm_syscall mov why, #0 b sys_ni_syscall @ 未实现的系统调用 ENDPROC(vector_swi)
  • 为什么异常向量入口__vectors_start有8个,但是vector_stub只有5个?
    刚才说的SVC,没有用vector_stub实现,剩下7个
    vector_rst没有用vector_stub实现,就两行代码,剩下6个:
vector_rst: ARM( swi SYS_ERROR0 ) THUMB( svc #0 ) THUMB( nop ) b vector_und

还有1个vector_addrexcptn没用vector_stub实现,那就正好剩5个了:

/*============================================================================= * Address exception handler *----------------------------------------------------------------------------- * These aren't too critical. * (they're not supposed to happen, and won't happen in 32-bit data mode). */ vector_addrexcptn: b vector_addrexcptn

小节

终于把异常向量的入口分析完了,到现在这么长,甚至还没见到内核IRQ的统一入口,还在架构里晃悠
不过这些分析还是有很多收获的!这里写一些可以记忆的结论性的东西,作为小节
字数一多,Gridea卡的不行了,这章就结束,另起一章吧

  • Cortex A7的异常向量
    Cortex A7的异常入口为__vectors_start,有8种异常:vector_rst、vector_und、__vectors_start + 0x1000(vector_swi)、vector_pabt、vector_dabt、vector_addrexcptn、vector_irq、vector_fiq,其中2个特别重要:vector_irq和vector_swi,一个是硬件中断的入口,一个是系统调用的入口,别的基本用不到。

  • Cortex A7 IRQ处理流程
    __vectors_start->vector_stub->__irq_usr/__irq_svc(视被中断的现场所处模式选择进入)

  • 上下文(非常重要)
    是否会嵌套:从触发IRQ那一刻,CPSR.I由硬件置1,到进入__irq_usr或者__irq_svc,这段时间都不可能被IRQ继续抢占或嵌套。
    现场保存在哪:SP寄存器的IRQ模式副本:SP_IRQ,软件保存的
    现场销毁了吗:到进入__irq_usr或者__irq_svc时,是要把SP_IRQ当参数传进去的,那当然没销毁
    CPU模式是什么:从触发IRQ那一刻,到进入__irq_usr或者__irq_svc前,都是IRQ模式,但随着进入__irq_usr或者__irq_svc,就自动切换成了SVC模式,后续都是在SVC模式了

  • 其他细节
    内核里有__irq_svc函数,说明IRQ是可以打断SVC的,这似乎说明IRQ的优先级比SVC高。

勘误

IRQ所打断的现场保存在SP_IRQ中,这句话确实没错
但是vector_stub保存现场的方式根本没移动SP指针:

@ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr

没有移动SP指针的汇编代码!这就意味着根本就不用惦记着出栈,下次进入IRQ自会覆盖!
由于本章代码在执行时,IRQ是关闭的,所以不会被错误抢占导致SP_IRQ被覆盖
在接下来的__irq_usr或__irq_svc中,SP_IRQ的现场就被拷贝到SP_SVC了,之后就算随便覆盖也没事了

http://www.jsqmd.com/news/669807/

相关文章:

  • [特殊字符] Meixiong Niannian画图引擎负面Prompt优化效果:去水印/去畸变实测
  • 【源码深度】Android 反射·注解·代理·AOP·Hook全解析|Android全栈体系150讲-25
  • PP-DocLayoutV3法律文书应用:合同/判决书/公证材料非规则排版智能分割
  • MinerU文档AI效果展示:工程图纸截图中尺寸标注+材料说明+工艺要求语义关联解析
  • 数字黑洞:揭秘6174的神奇数学现象
  • 手把手实战:用阿里云ECS从零搭建一套可用的VOS测试环境(含SIP线路对接调试)
  • 一键体验GPT-SoVITS:Docker部署+语音合成实战教程
  • 【2026奇点大会权威解码】:AGI如何重构全球能源管理范式?3大颠覆性技术路径首次公开
  • 模块解耦的重要性
  • DDColor镜像灰度发布:A/B测试不同模型版本着色效果的实施方案
  • BGE-Large-Zh效果展示:天气预报查询与气象文档匹配的语义精准度验证
  • Qwen3-0.6B-FP8实战教程:API接口测试与LLM应用框架无缝对接
  • Windows11安装VC++6.0中文版全攻略
  • SITS2026到底测什么?3大认知维度、7类推理任务、12项泛化指标全拆解:AGI开发者不可错过的准入标尺
  • 基于java的叙事之眼系统自动化测试
  • Spring with AI (): 评估答案——UnitTest引入
  • MySQL中如何使用UPPER转大写字母_MySQL文本格式化函数
  • RMBG-2.0功能体验:蒙版查看、一键下载,完整操作流程
  • LeetCode 594题‘磁带利用率’详解:从背包DP到贪心交换,附C++完整代码与三大易错点
  • 5分钟部署Qwen2.5-VL-7B视觉模型:Ollama让多模态AI触手可及
  • 用了5款降AI率工具后,到底哪个好?真实排名告诉你
  • Fish Speech 1.5语音合成AB测试:不同temperature下自然度主观评分对比
  • 忍者像素绘卷入门必看:5分钟完成Python环境安装与首次调用
  • 第32篇:AI数据标注——隐藏在巨头身后的百亿级市场与入门指南(概念入门)
  • Qwen3-VL-2B与HuggingFace模型对比:本地部署体验差异
  • 降AI率工具哪个好用?看完这篇手把手教你3步选对
  • 零代码体验NaViL-9B:上传图片自动问答,多模态AI快速上手
  • 避坑指南:STM32CubeMX配置FMC驱动LCD时常见的5个低级错误(附ILI9488调试记录)
  • Vision Transformer (ViT) 技术解析
  • 关于explorer.exe报错,及原因