标签 ARM 下的文章
ARMv7-A 那些事 - 7.栈回溯浅析在嵌入式开发过程中,经常需要对代码进行调试来解决各种各样的问题,常用的调试手段有:(1)、开发环境搭配硬件仿真器进行在线调试。优点:调试过程中能够清楚的知道各个寄存器的值以及各个变量的值,程序的执行流程也能够一目了然。缺点:板卡需要引出硬件仿真器的连接口,并且需要购买硬件仿真器。(2)、通过调试串口打印信息梳理程序的执行流程,结合代码分析问题产生的原因。优点:足够简单,通过增加较多的打印信息来分析问题出现的位置,再结合代码分析问题产生的原因。缺点:没法准确的定位问题产生的位置和原因。(3)、在应用或者操作系统死机的时候,根据操作系统输出的异常栈信息进行分析,再结合镜像或者应用的反汇编代码进行定位。通常这种方法和方法(2)结合使用。本文主要简单的讲讲栈回溯,对于以后去理解操作系统的异常栈处理打个基础吧。ARM处理器的栈回溯主要有两种方式:一种是基于栈帧寄存器(FP)的栈回溯,另一种是unwind形式的栈回溯。本文主要讲讲基于栈帧寄存器(FP)的栈回溯。栈回溯相关寄存器在栈回溯过程中,主要涉及如下寄存器:R15:又叫程序计数器(Program Counter)PC,PC主要用于存放CPU取指的地址。R14:又叫链接寄存器(Link register)LR,LR主要用于存放函数的返回地址,即当函数返回时,知道自己该回到哪儿去继续运行。R13:又叫堆栈指针寄存器(Stack pointer)SP,SP通常用于保存堆栈地址,在使用入栈和出栈指令时,SP中的堆栈地址会自动的更新。R12:又叫内部过程调用暂存寄存器(Intra-Procedure-call scratch register)IP,主要用于暂存SP。R11:又叫帧指针寄存器(Frame pointer)FP,通常指向一个函数的栈帧底部,表示一个函数栈的开始位置。ARM栈帧结构依据AAPCS (ARM Archtecture Procedure Call Standard)规范,当调用子函数时,子函数一开始的代码总是会执行压栈操作来保留父函数的相关信息,压栈步骤示例如下所示:每个函数都有自己的栈空间,这一部分称为栈帧。栈帧在函数被调用的时候创建,在函数返回后销毁。每个函数的栈帧是由SP寄存器和FP寄存器来界定的,ARM栈帧结构典型示意图如下所示:上图描述的栈帧,main函数和func1函数的示意代码如下:每个函数的栈帧中都会保存调用该函数之前的PC、LR、SP、FP寄存器的值;如果函数具有参数并且函数内部使用了局部变量,那么函数栈帧中也会保存函数的参数和局部变量;如果被调用的子函数参数过多,那么多余的参数会通过父函数的栈进行传递。比如func1函数的参数p5通过main函数的栈帧进行传递的。(注:编译器的版本不同,函数栈帧中参数和局部变量的压栈顺序可能不同,PC,LR,SP和FP这4个寄存器的压栈顺序一般是固定的)函数栈帧中的PC和LR均指向代码段,PC表示执行入栈指令时CPU正在取指的地址,LR表示当前函数返回后继续执行的地址。栈回溯原理在栈回溯的过程中,我们主要利用FP寄存器进行栈回溯。通过FP就可以知道当前函数的栈底,从而可以找到存储在栈帧中的LR寄存器的数据,这个数据就是函数的返回地址。同时也可以找到保存在函数栈帧中的上一级函数FP的数据,这个数据指向了上一级函数的栈底,按照同样的方法可以找出上一级函数栈帧中存储的LR和FP数据,就知道哪个函数调用了上一级函数以及这个函数的栈底地址。这就是栈回溯的流程,整个流程以FP为核心,依次找出每个函数栈帧中存储的LR和FP数据,计算出函数返回地址和上一级函数栈底地址,从而找出每一级函数调用关系。栈回溯编译选项当gcc的编译选项带有-mapcs-frame时,编译出来的代码能够将PC,LR,SP和FP寄存器的值压入函数的栈帧中。默认情况下gcc的编译选项为-mno-apcs-frame,此时编译出来的代码不一定会将PC,LR,SP和FP这四个寄存器的值压入函数的栈帧中,可能只会将LR和FP寄存器的值压入函数的栈帧中。关于-mapcs-frame选项,gcc的手册描述如下:我这里使用的gcc信息如下:虽然gcc手册上说-mapcs-frame选项被废弃了,但是只有添加了该选项,编译出来的代码才会将PC,LR,SP和FP寄存器的值压入函数的栈帧中。我这里编译代码仍然使用-mapcs-frame选项,有知道该选项对应的新的栈帧配置选项的兄弟可以告知我一下。栈回溯示例根据前面的内容,这里简单的写了一个栈回溯的示例,函数调用流程为:main -> test_a -> test_b -> test_c。函数的源代码如下:上述函数的反汇编内容如下:当程序运行到test_c()函数的return c;代码处时,FP的值为0x9FDFFF94,此时内存数据如下:test_c()函数的栈底为0x9FDFFF94,可以得到test_c()函数栈帧中LR为0x800021C8、FP为0x9FDFFFB4,LR是test_c()函数执行完成后的返回地址,与反汇编代码中test_b()函数调用完test_c()之后的下一个执行地址一致:FP为0x9FDFFFB4表示test_b()函数的栈底为0x9FDFFFB4,有了test_b()函数的栈底就可以得到test_b()函数栈帧中LR为0x80002194、FP为0x9FDFFFDC,从而知道test_b()函数执行完成后的返回地址以及test_a()函数的栈底,依次逐级回溯,就可以知道程序的整个运行流程了。在栈回溯的过程中我们可以利用addr2line工具辅助我们对程序执行流程的分析。
ARMv7-A 那些事 - 6.常用汇编指令对于搞嵌入式驱动或者操作系统的人来说,掌握汇编语言的使用还是比较重要的,毕竟有时候在分析定位问题的时候,多多少少都会有汇编的身影。本文主要讲讲ARM指令集格式以及常用的ARM汇编指令(主要包括LDR和STR指令,LDM和STM指令,push和pop指令,MOV指令,CPS指令,MRS和MSR指令,MRC和MCR指令,其余指令暂时没列出来,用到时可以查看ARM手册进行了解)。ARM指令集格式ARMv7架构是一个32位的处理器架构。同时ARM架构是一个加载/存储体系结构,所有的数据处理操作需要在通用寄存器中完成。要学习了解处理器的汇编指令,那么首先可以看看汇编指令的通用表达式,具体的指令也就是使用具体的指令和参数代替通用表达式的参数。ARM指令集的指令表达如下所示:opcode{<cond>}{S} <Rd>, <Rn> {, <Rm>}opcode:指令助记符,比如LDR,STR,MOV等。{}:大括号括起来的内容表示可选。<>:<>括起来的内容是必须的。cond:条件码,比如EQ,NE,CS等,条件码的内容如下图所示:S:可选的后缀,如果指令中添加了S,那么指令的执行结果将会影响到CPSR寄存器的标志位域。Rd:目标寄存器。Rn:第一个操作数寄存器。Rm:第二个操作数寄存器。在了解了ARM指令的表达式之后,下面就讲讲常用的汇编指令。LDR和STR指令LDR指令用于从内存中读取数据存储到通用寄存器中。STR指令用于将通用寄存器中的值存储到内存中。LDR指令的语法如下所示:STR指令的语法如下所示:type:操作的数据宽度,可以是:B(unsigned byte),SB(signed byte),H(unsigned halfword),SH(signed halfword)。cond:条件码。Rt:目标寄存器。Rn:存储内存操作基地址的寄存器。Rm:存储偏移量的寄存器。offset:立即数。!:如果存在,表示最终的地址要写回Rn。T:表示处理器是在用户模式下访问内存地址。加载存储指令有4种寻址方式,LDR的操作描述如下(STR指令的操作类似):寄存器寻址:要寻址的地址存放在寄存器中。前变基寻址:在内存访问之前,将寄存器中的内存地址加上偏移量之后作为新的内存地址进行内存访问。指令形式为:LDR Rt, [Rn, Op2]。偏移量Op2可以是正数或者是负数,可以是一个立即数,可以是另一个寄存器的值,可以是另一个寄存器中的数据进行移位之后的值。带写回的前变基寻址:指令形式为:LDR Rt, [Rn, Op2]!。该寻址模式和前变基寻址一样,只是在访问完内存之后Rn寄存器中的值就更新为运算之后得到的新内存地址的值。带写回的后变基寻址:指令形式为:LDR Rt, [Rn], #offset和LDR Rt, [Rn], +/-Rm。将寄存器Rn中存储的数值作为内存地址,将该内存地址中的数据读出来存储到Rt寄存器中,然后将内存地址加减立即数offset或者Rm寄存器中的数值得到新的内存地址存储到Rn寄存器中。上面说的这些有可能不太好懂,下面简单的列举几个例子吧:STR指令的操作和LDR指令类似,这里就不列举了。LDR伪指令LDR相关的伪指令语法如下所示:下面是LDR伪指令简单的使用:LDM和STM指令LDM指令用于加载指定地址上的数据保存到一个或者多个寄存器中。STM指令用于将一个或者多个寄存器中的数据存储到指定地址上。LDM和STM指令主要用于现场保护和数据复制。LDM指令的语法如下所示:LDM{addr_mode}{cond} Rn{!},reglist{^}STM指令的语法如下所示:STM{addr_mode}{cond} Rn{!},reglist{^}addr_mode:地址模式,用于数据块传输的地址模式,如下所示:也可以使用相应的面向堆栈的寻址模式,如下所示:cond:条件码。Rn:Rn存储了用于传输的初始地址。!:如果存在,表示最终的地址要写回Rn。reglist:用{}括起来的一个寄存器或者多个寄存器组成的列表。它可以是一个寄存器范围。如果{}中的寄存器超过一个,那么寄存器或者寄存器范围之间通过逗号(,)分隔。^:如果在除了USR模式和SYS模式下存在该符号,意味着将发生下述的两个动作:当寄存器列表中不包含PC时,加载/存储的是USR模式的寄存器,而不是当前模式的寄存器。在使用LDM指令时,如果寄存器列表中包含PC时,那么除了正常的多寄存器传送外,会将SPSR 拷贝到CPSR 中,这可用于异常处理返回。上面的内容可能不是很好理解,下面简单的列举写例子:LDMIA和STMIA例子LDMIA例子如下所示:STMIA例子如下所示:LDMIB和STMIB例子LDMIB例子如下所示:STMIB例子如下所示:LDMDA和STMDA例子LDMDA例子如下所示:STMDA例子如下所示:LDMDB和STMDB例子LDMDB例子如下所示:STMDB例子如下所示:现场保护在数据块的传输中:STMDB和LDMIA对应使用,STMIA和LDMDB对应使用。在堆栈操作中:STMFD和LDMFD对应使用,STMFA和LDMFA对应使用。在子程序或者异常处理时,使用LDMFD和STMFD进行现场保护的例子如下:同样的可以使用STMDB和LDMIA指令进行现场保护,因此上述代码可以修改成下述形式:push和pop指令push和pop指令主要用于子程序或者异常的现场保护。push指令用于将寄存器内容压入堆栈。pop指令用于将堆栈中的内容恢复到寄存器中。push指令的语法如下所示:PUSH{cond} reglistpop指令的语法如下所示:POP{cond} reglistcond:条件码。reglist:用{}括起来的一个寄存器或者多个寄存器组成的列表。它可以是一个寄存器范围。如果{}中的寄存器超过一个,那么寄存器或者寄存器范围之间通过逗号(,)分隔。push指令等价于STMDB指令。pop指令等价于LDMIA指令。使用push指令和pop指令保护现场的例子如下所示:MOV指令MOV指令主要用于将数据搬移到寄存器中。MOV指令的语法如下所示:S:可选的后缀,如果指令中添加了S,那么指令的执行结果将会影响到CPSR寄存器的标志位域。cond:条件码。Rn:目标寄存器。Rm:源寄存器。imm:立即数。MOV指令的使用例子如下:CPS指令可以通过CPS(Change Processor State)指令来修改处理器模式。CPS指令也可以用来使能或者禁止异常。CPS指令的语法如下所示:mode是处理器的模式编码,比如在从其他模式下切换到SYS模式,使用下述代码即可:IE使能中断或者终止。ID禁止中断或者终止。iflags由下面的一种或者几种组成:a:表示异步终止(asynchronous abort);i:表示中断(IRQ);f:表示快中断(FIQ);下述代码是CPS指令的一些简单用法:MRS与MSR指令MRS和MSR指令可用于读写程序状态寄存器CPSR,APSR和SPSR。在ARM处理器中,只有MRS指令可以从程序状态寄存器CPSR,APSR和SPSR中读出数据到通用寄存器中。MRS指令操作程序状态寄存器的语法如下:MRS{cond} Rd, psrcond为条件码。Rd为目标寄存器,Rd不允许为R15。psr为程序状态寄存器CPSR,APSR或者SPSR。MRS指令的示例代码如下所示:MSR指令可以用来写程序状态寄存器CPSR,APSR和SPSR的全部或者部分域。MSR指令操作程序状态寄存器的语法如下:cond为条件码。psr为程序状态寄存器CPSR或者SPSR。constant是一个8位立即数。ARM文档对于constant的介绍如下:constant is an 8-bit pattern rotated by an even number of bits within a 32-bit word. (Not available in Thumb.)Rm是源寄存器。fields由下面的一个或者多个组合而成:c:xPSR[7:0],控制位域;x:xPSR[15:8],扩展位域;s:xPSR[23:16],状态位域;f:xPSR[31:24],标志位域;MSR指令的示例代码如下所示:只有在除用户模式外的其他模式下才能够修改状态寄存器。MRC和MCR指令ARMv7-A体系结构的处理器提供了MRC和MCR指令用于对协处理器进行读写操作。MRC指令用于将协处理器中的寄存器数据读取到ARM通用寄存器中。MCR指令用于将ARM通用寄存器中的数据写入到协处理器的寄存器中。MRCMRC指令的语法如下所示:MRC{cond} coproc, opc1, Rt, CRn, CRm{, opc2}cond为条件码。coproc为协处理器名称,CP0~CP15协处理器分别对应名称p0~p15。opc1为协处理器要执行的操作码,取指范围为0~7。Rt为ARM通用寄存器,用于存储读取到的协处理器寄存器数据。CRn为协处理器寄存器,对于CP15协处理器来说,CRn取值范围为c0~c15。CRm为协处理器寄存器,对于CP15协处理器来说,通过CRm和opc2一起来确定CRn对应的具体寄存器。opc2为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。MRC指令使用示例如下:MCRMCR指令的语法如下所示:MCR{cond} coproc, opc1, Rt, CRn, CRm{, opc2}cond为条件码。coproc为协处理器名称,CP0~CP15协处理器分别对应名称p0~p15。opc1为协处理器要执行的操作码,取指范围为0~7。Rt为ARM通用寄存器,用于存储要写入到协处理器寄存器中的数据。CRn为协处理器寄存器,对于CP15协处理器来说,CRn取值范围为c0~c15。CRm为协处理器寄存器,对于CP15协处理器来说,通过CRm和opc2一起来确定CRn对应的具体寄存器。opc2为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。MCR指令使用示例如下:
ARMv7-A 那些事 - 5.CP15协处理器协处理器概述ARM架构通过支持协处理器来扩展处理器的功能。ARM架构的处理器支持最多16个协处理器,通常称为CP0~CP15。下述的协处理器被ARM用于特殊用途:CP15:提供系统控制功能,主要用于配置MMU、TLB和Cache等功能。CP14:主要用于控制系统Debug功能。CP10、CP11:两个协处理器一起提供了对浮点运算和向量操作的支持,这两个协处理器主要用于控制和配置浮点功能和高级SIMD指令扩展。其他协处理器被ARM保留用于将来使用。本文主要说说CP15协处理器。CP15协处理器总览CP15是系统控制协处理器,主要用于对ARM处理器核心支持的许多特性功能进行配置。CP15协处理器支持16个32位主寄存器(primary register),命名为c0~c15。c0~c15主寄存器各自又有多个32位的物理寄存器(physical register)。CP15协处理器的大多数寄存器不能在USR模式下访问,只能在除USR模式外的其他模式下访问。下面列出c0~c15中比较常用的寄存器:primary registerphysical register描述c0MIDR主ID寄存器,用于记录版本信息c0MPIDR多核处理器情况下,提供一种方法来唯一标识集群中的各个核心c1SCTLR系统控制寄存器c1ACTLR辅助控制寄存器c1CPACR协处理器访问控制寄存器,控制访问除了CP14和CP15的协处理器c1SCR安全配置寄存器,被TrustZone使用c2、c3TTBR0一级转换页表基址寄存器0c2、c3TTBR1一级转换页表基址寄存器1c2、c3TTBCR页表转换控制寄存器c5、c6DFSR数据异常(Data Fault)状态寄存器c5、c6IFSR指令异常(Instruction Fault)状态寄存器c5、c6DFAR数据异常(Data Fault)地址寄存器c5、c6IFAR指令异常(Instruction Fault)地址寄存器c7branch predictorcache和分支预测管理功能c7barrier数据和指令屏障操作c8TLBTLB操作c9performance monitors性能监视器c12VBAR提供非监视模式处理异常的异常基地址c12MVBAR提供监视模式处理异常的异常基地址c13CONTEXTIDR上下文ID寄存器c15CBAR配置基址寄存器,为GIC和本地时钟类型外设提供基地址在CP15协处理器中,c0~c15每个主处理器下面有多个物理寄存器,上述表格只列出了部分常用的物理寄存器。协处理器操作指令ARMv7-A体系结构的处理器提供了MRC和MCR指令用于对协处理器进行读写操作。MRC指令用于将CP15协处理器中的寄存器数据读取到ARM通用寄存器中。MCR指令用于将ARM通用寄存器中的数据写入到CP15协处理器的寄存器中。MRCMRC指令的语法如下所示:MRC{cond} coproc, opc1, Rt, CRn, CRm{, opc2}cond为条件码。coproc为协处理器名称,CP0~CP15协处理器分别对应名称p0~p15。opc1为协处理器要执行的操作码,取指范围为0~7。Rt为ARM通用寄存器,用于存储读取到的协处理器寄存器数据。CRn为协处理器寄存器,对于CP15协处理器来说,CRn取值范围为c0~c15。CRm为协处理器寄存器,对于CP15协处理器来说,通过CRm和opc2一起来确定CRn对应的具体寄存器。opc2为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。MRC指令使用示例如下:MCRMCR指令的语法如下所示:MCR{cond} coproc, opc1, Rt, CRn, CRm{, opc2}cond为条件码。coproc为协处理器名称,CP0~CP15协处理器分别对应名称p0~p15。opc1为协处理器要执行的操作码,取指范围为0~7。Rt为ARM通用寄存器,用于存储要写入到协处理器寄存器中的数据。CRn为协处理器寄存器,对于CP15协处理器来说,CRn取值范围为c0~c15。CRm为协处理器寄存器,对于CP15协处理器来说,通过CRm和opc2一起来确定CRn对应的具体寄存器。opc2为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0。MCR指令使用示例如下:CP15协处理器主寄存器组成CP15协处理器有c0~c15总共16个主寄存器,在每个主寄存器下面,又有多个物理寄存器。下图总结了CP15协处理器的寄存器组织形式:上图对于MRC和MCR指令所要使用到的一些参数都标明了,对于想要访问CP15协处理器相关寄存器,只需要看图填写好参数就行了。下面具体列一下c0~c15各个主寄存器的组成。CP15协处理器c0寄存器组成主寄存器c0主要提供ID相关的功能,c0寄存器的组成如下图所示:CP15协处理器c1寄存器组成主寄存器c1主要提供系统控制相关的功能,c1寄存器的组成如下图所示:在CP15协处理器的寄存器中,系统控制寄存器SCTLR是被访问的比较多的寄存器。对SCTLR寄存器的访问需要在PL1或者更高的特权等级。SCTLR寄存器的位关系如下图所示:位标志说明30TEThumb异常使能,控制在异常发生时(包括reset),将会进入哪种指令集,0:ARM指令集,1:Thumb指令集27NMFI不可屏蔽的FIQ支持,0:软件可以通过写CPSR.F位来屏蔽FIQ,1:软件不可以通过写CPSR.F位来屏蔽FIQ25EE在进入异常处理时的大小端模式配置,0:小端,1:大端22U表明是否使用对齐模式21FIFIQ配置使能13V选择异常向量表基址,0:0x00000000,1:0xffff000012I指令cache使能11Z分支预测使能2C数据cache使能1A对齐检查使能0MMMU使能CP15协处理器c2 c3寄存器组成主寄存器c2和c3主要提供内存保护和内存控制相关的功能,c2和c3寄存器的组成如下图所示:CP15协处理器c4寄存器组成在任何基于ARMv7实现的处理器中,协处理器CP15的c4寄存器没有被使用。CP15协处理器c5 c6寄存器组成主寄存器c5和c6主要提供内存系统错误上报功能,c5和c6寄存器的组成如下图所示:CP15协处理器c7寄存器组成主寄存器c7主要提供cache维护,地址转换和内存屏障操作相关的功能,c7寄存器的组成如下图所示:CP15协处理器c8寄存器组成主寄存器c8主要提供TLB维护相关的功能,c8寄存器的组成如下图所示:CP15协处理器c9寄存器组成主寄存器c9保留用于分支预测,cache和TCM操作,c9寄存器的组成如下图所示:CP15协处理器c10寄存器组成主寄存器c10主要提供内存重映射和TLB控制相关的功能,c10寄存器的组成如下图所示:CP15协处理器c11寄存器组成主寄存器c11保留用于TCM DMA操作,c11寄存器的组成如下图所示:CP15协处理器c12寄存器组成主寄存器c12提供安全扩展功能,c12寄存器的组成如下图所示:CP15协处理器c13寄存器组成主寄存器c13提供进程ID、上下文ID和线程ID处理功能,c13寄存器的组成如下图所示:CP15协处理器c14寄存器组成主寄存器c14保留用于通用定时器功能,c14寄存器的组成如下图所示:CP15协处理器c15寄存器组成主寄存器c15由处理器实现决定。这里只是简单的将CP15各个主寄存器的组成列出来了,方便在使用MRC和MCR指令配置CP15主寄存器时,查看指令各个参数的设置,以及对照配置的具体寄存器,至于寄存器的具体内容由于篇幅原因就不列出来了,CP15寄存器的细节可以参考ARMv7AR手册的B3.17章节内容。
ARMv7-A 那些事 - 4.处理器模式与特权等级对于现代操作系统,通常情况下用户的应用程序运行在用户态,操作系统内核运行在内核态。用户态的应用对于系统硬件资源的访问是受限的,内核态则能够访问所有的系统硬件资源。操作系统的用户态和内核态是根据处理器的特权等级和运行模式进行硬件隔离的,这也极大的提高了操作系统的安全性。安全扩展和虚拟化扩展ARMv7-A体系结构支持安全扩展和虚拟化扩展。当处理器实现了安全扩展之后,处理器就存在普通世界(Normal world)和安全世界(Secure world)这两个世界,这在硬件层面上就可以将敏感数据和要求在安全环境运行的应用和普通应用完全隔离,如下图所示:当处理器实现了虚拟化扩展之后,处理器就新增了一个hypervisor mode (Hyp),并且也新增了一个特权级模式PL2。支持虚拟化扩展的处理器示意图如下所示:虚拟化扩展允许在Normal world运行多个操作系统,Hypervisor只能运行在Normal world。Secure world可以运行Trusted OS和Trusted services。特权等级处理器的模式,特权等级和安全状态的关系如下图所示:本文仅讨论非安全状态。在非安全状态下,存在3种特权等级(Privilege level):PL0、PL1和PL2,描述如下:PL0:用户模式(User mode)运行的应用程序处于PL0特权等级。运行在用户模式的程序被称为非特权程序。非特权程序对于系统资源的访问是受限的,对应Linux的用户态。PL1:除了用户模式和Hyp模式外,其他模式下的程序执行都处于PL1特权等级。PL1模式是指除了用户模式和Hyp模式之外的其他模式。操作系统运行在PL1特权级。PL2:如果实现了虚拟化扩展,Hyp模式运行的系统管理程序处于PL2特权等级。系统管理程序将控制并启用多个操作系统在同一个处理器系统上共存和执行。处理器模式ARMv7-A体系结构提供了9种处理器模式,如下图所示:从上图可以知道ARMv7-A提供的处理器模式有User、FIQ、IRQ、Supervisor(SVC)、Monitor(MON)、Abort(ABT)、Hyp、Undefined(UND)、System(SYS)模式,各个处理器模式的描述如下:User:用户模式,用户程序运行在User模式下,拥有受限的系统资源访问权限。FIQ:快中断异常处理模式,发生FIQ中断时的处理器模式,相对于中断而言,快中断拥有更高的响应等级和更低的延迟。IRQ:中断异常处理模式,发生IRQ中断时的处理器模式。Supervisor(SVC):管理员模式,操作系统内核通常运行在该模式下,在处理器复位或者应用程序调用svc指令的时候将会进入到该模式,系统调用就是通过svc指令完成的。Abort(ABT):异常终止模式,当发生Data Abort exception或者Prefetch Abort exception异常的时候进入这个模式。Undefined(UND):未定义指令模式,当执行未定义指令时进入这个模式。System(SYS):系统模式,系统模式和用户模式共享寄存器视图,并且目前大多数系统未使用该模式,利用这个特性我们可以在处理器启动时通过设置系统模式的SP寄存器来达到设置用户模式堆栈的目的,要设置用户模式的其他寄存器也可以这样操作。Monitor(MON):监视模式,实现了安全扩展的处理器才有该模式,在该模式下执行Secure和Non-secure处理器状态的切换。Hyp:实现了虚拟化扩展的处理器才有该模式。User模式处于PL0特权等级。FIQ、IRQ、Supervisor(SVC)、Monitor(MON)、Abort(ABT)、Undefined(UND)、System(SYS)这些模式处于PL1特权等级。Hyp模式处于PL2特权等级。本文不讨论支持安全扩展和虚拟化扩展的场景,因此对于Monitor模式和Hyp模式也不做深入探讨。通常情况下,应用程序运行在User模式(PL0),运行在User模式下的应用程序对硬件没有直接访问权,所有的硬件操作都需要通过系统调用向内核进行申请。操作系统内核运行在管理员模式(PL1),对系统调用、中断、异常等系统事件进行响应、处理并返回,以这种硬件隔离的方式保证了操作系统内核的安全。以Linux操作系统为例,虽然ARMv7-A体系结构的处理器有9种模式,但是操作系统只工作在SVC和USR模式,SVC处于内核态,USR处于用户态。至于其他的异常模式,Linux只是简单的略过。比如中断模式irq,Linux只有很短的汇编代码在irq模式运行,主要是保存上下文,然后就立马切换到了SVC模式,由内核进行统一处理。寄存器集ARMv7-A体系结构的处理器在不同处理器模式下,对于通用寄存器的使用情况也有所不同,如下图所示:上图中蓝色背景的寄存器属于bank寄存器,也就是相同的寄存器名对应不同的寄存器实体。从上图可以看出:R0~R7,PC在所有模式下是共享的。系统模式和用户模式共享寄存器视图,系统模式没有bank寄存器。FIQ 模式下,R8~R12、SP、LR 都是该模式专门的寄存器,FIQ比IRQ响应和处理速度更快,也得益于FIQ模式具有比IRQ模式更多的bank寄存器。FIQ、IRQ、ABT、SVC和UND模式,都有他们自己模式下专用的SP和LR,也就是说,在模式切换的时候,不需要针对这两个寄存器进行现场保护和恢复;FIQ、IRQ、ABT、SVC和UND模式,都有他们自己模式下专用的SPSR。在处理器发生中断或者异常时,处理器会自动的从一个模式A进入到另一个模式B,模式A的CPSR/APSR将会自动保存到模式B的SPSR中,这样模式B中的处理程序能够通过访问SPSR寄存器得到模式A下CPSR寄存器的信息。处理器模式切换ARMv7-A体系结构的处理器,处理器模式是由状态寄存器CPSR的M域(BIT[4:0])来控制的。对于用户模式而言,是没有权限操作CPSR寄存器的M域的,只能通过svc指令进入到SVC模式。对于SYS、FIQ、IRQ、ABT、SVC和UND模式而言,可以通过给CPSR寄存器的M域赋值来达到切换处理器模式的目的。各个模式的编码如下图所示:下述代码简单的演示了处理器模式切换:在上述代码中,使用了cps #mode指令来完成处理器模式的切换,在切换到相应模式之后,设置了对应模式的堆栈。在上述代码中,为什么不直接切换到USR模式,再设置USR模式的堆栈,而要借助SYS模式来设置USR模式的堆栈呢?这个问题相信大家在学习了本节内容之后,应该还是比较简单的。
ARMv7-A 那些事 - 3.程序状态寄存器程序状态寄存器的作用就是反映处理器的状态信息。在程序运行期间我们可以通过查看程序状态寄存器的状态位来进行程序的分支跳转处理,或者我们可以设置程序状态寄存器的模式位来改变处理器的运行模式,或者我们可以设置程序状态寄存器的中断屏蔽位来屏蔽中断。在任何时刻,我们可以访问处理器的16个寄存器(R0~R15)和当前程序状态寄存器(Current Program Status Register,CPSR)。用户模式下的程序访问的程序状态寄存器叫做APSR(Application Program Status Register),APSR是CPSR在用户模式下的别名,因为在用户模式下CPSR的部分域是不能操作的,因此CPSR的部分域被屏蔽后就是APSR。CPSR寄存器组成在所有模式下均可以访问到CPSR,只是在用户模式下CPSR的部分域是不能操作的,当前程序状态寄存器(CPSR)的位组成如下图所示:各个位域的说明如下:位标志说明31N当运算结果为负且运算指令要求更新寄存器时,该位会被置位。30Z当运算结果为0且运算指令要求更新寄存器时,该位会被置位。29C当运算结果产生进位且指令要求更新寄存器时,该位会被置位。28V当运算结果产生符号位溢出且指令要求更新寄存器时,该位会被置位。27Qcumulative saturation。26:25IT[1:0]IT位,由IT[7:2]和IT[1:0]组成,Thumb指令集中IT指令的If-Then执行状态。24J指示ARM是否处于Jazelle状态。19:16GE[3:0]被一些SIMD(Single Instruction Multiple Data)指令使用。15:10IT[7:2]见IT[1:0]的描述。9E指示处理器的大小端模式,同时可以通过设置该位来修改处理器的大小端模式,1表示大端模式,0表示小端模式。8A是否屏蔽异步终止,该位为1时表示屏蔽异步终止,为0时表示打开异步终止。7I是否屏蔽IRQ,该位为1时表示屏蔽IRQ,为0时表示打开IRQ。6F是否屏蔽FIQ,该位为1时表示屏蔽FIQ,为0时表示打开FIQ。5T指示ARM是否处于Thumb状态。J和T标志共同决定处理器使用的指令集。J=0,T=0:ARM指令集;J=0,T=1:Thumb指令集;J=1,T=0:Jazelle指令集;J=1,T=1:ThumbEE指令集。4:0M[4:0]指示处理器的模式,同时可以通过设置该位域来修改处理器的模式。处理器各个模式的编码如下图所示:APSR寄存器组成在用户模式下,用户程序能够操作的CPSR寄存器位域是有限制的,对CPSR寄存器的部分位域屏蔽之后就是APSR了,应用程序状态寄存器(APSR)的位组成如下图所示:从上图可以看出,APSR只能访问N,Z,C,V,Q和GE[3:0]这些标志位,这些标志位的含义和CPSR中对应标志位的含义一样。SPSR备份程序状态寄存器(Saved Program Status Register,SPSR)主要用于存储前一个执行模式的CPSR。FIQ、IRQ、ABT、SVC和UND模式,都有他们自己模式下专用的SPSR。在处理器发生中断或者异常时,处理器会自动的从一个模式A进入到另一个模式B,模式A的CPSR/APSR将会自动保存到模式B的SPSR中,这样模式B中的处理程序能够通过访问SPSR寄存器得到模式A下CPSR寄存器的信息。程序状态寄存器操作指令CPS指令可以通过CPS(Change Processor State)指令来修改处理器模式。CPS指令也可以用来使能或者禁止异常。CPS指令的语法如下所示:mode是处理器的模式编码,比如在从其他模式下切换到SYS模式,使用下述代码即可:IE使能中断或者终止。ID禁止中断或者终止。iflags由下面的一种或者几种组成:a:表示异步终止(asynchronous abort);i:表示中断(IRQ);f:表示快中断(FIQ);下述代码是CPS指令的一些简单用法:MRS与MSR指令MRS和MSR指令可用于读写程序状态寄存器CPSR,APSR和SPSR。在ARM处理器中,只有MRS指令可以从程序状态寄存器CPSR,APSR和SPSR中读出数据到通用寄存器中。MRS指令操作程序状态寄存器的语法如下:MRS{cond} Rd, psrcond为条件码。Rd为目标寄存器,Rd不允许为R15。psr为程序状态寄存器CPSR,APSR或者SPSR。MRS指令的示例代码如下所示:MSR指令可以用来写程序状态寄存器CPSR,APSR和SPSR的全部或者部分域。MSR指令操作程序状态寄存器的语法如下:cond为条件码。psr为程序状态寄存器CPSR或者SPSR。constant是一个8位立即数。ARM文档对于constant的介绍如下:constant is an 8-bit pattern rotated by an even number of bits within a 32-bit word. (Not available in Thumb.)Rm是源寄存器。fields由下面的一个或者多个组合而成:c:xPSR[7:0],控制位域;x:xPSR[15:8],扩展位域;s:xPSR[23:16],状态位域;f:xPSR[31:24],标志位域;MSR指令的示例代码如下所示:只有在除用户模式外的其他模式下才能够修改状态寄存器。
ARMv7-A 那些事 - 2.通用寄存器与流水线世界上有很多种体系结构的处理器,比较知名的处理器体系结构有:ARM、x86、RISC-V、mips、LoongArch、PowerPC等。不论是哪一种架构的处理器,其处理器核心都会自带一定数量的寄存器,这些寄存器在处理器核心的运行过程中发挥着基础而又重要的作用。ARM体系结构是一种基于指令加载和存储的体系结构。在这种体系结构下,所有的数据处理都需要在通用寄存器中完成,而不能直接在内存中完成。因此,这种体系结构的处理器核心处理数据的过程为:首先把待处理数据从内存加载到通用寄存器,然后进行处理,最后把结果写入内存中。通用寄存器ARM架构提供了16个32位通用寄存器(R0-R15)用于软件使用。其中R0-R12是普通寄存器,R13、R14和R15在程序的运行过程中通常用作固定的用途。R13:又叫堆栈指针寄存器(Stack pointer)SP,SP通常用于保存堆栈地址,在使用入栈和出栈指令时,SP中的堆栈地址会自动的更新。堆栈主要用于保存局部变量,保存函数间调用的关键寄存器。对于根本不需要进行堆栈操作的程序,SP可以当做普通寄存器来存储数据。R14:又叫链接寄存器(Link register)LR,LR主要用于存放函数的返回地址,即当函数返回时,知道自己该回到哪儿去继续运行。通常链接寄存器是和BL/BLX/CALL指令搭配使用,这几个指令被调用后,默认会自动将当前调用指令的下一条指令地址保存到LR寄存器当中。R15:又叫程序计数器(Program Counter)PC,PC主要用于存放CPU取指的地址。ARMv7架构同时支持ARM指令集和Thumb指令集。在ARM指令集中,当CPU正在执行A指令时,PC的值为当前指令A地址+8;在Thumb指令集中,由于Thumb指令集为16位,当CPU正在执行A指令时,PC的值为当前指令A地址+4。但是当手动向PC赋值时,CPU就会跳转到赋值所代表的地址处去运行。记住PC存放的是取指地址,不是当前CPU运行地址。备注:在ARM状态下,PC指向的地址bit[1:0]总是为0,因此PC指向的地址都是4字节对齐。ARMv7架构的处理器支持混合编码即同时支持ARM指令集和Thumb指令集,因此为了区分Thumb指令集和ARM指令集,ARM将PC指向地址的bit[0]位作为标志位。如果PC指向的地址bit[0]位为1,表示当前是Thumb指令集;如果PC指向的地址bit[0]位为0,表示当前是ARM指令集。三级流水线为了增加处理器指令流的速度,ARM使用了多级流水线技术。多级流水线技术是一种将指令的执行分解成多个步骤,并让不同指令的各步骤重叠的一种准并行处理实现技术。经典的三级流水线结构将指令的执行分成取指,译码和执行这三个阶段。可以将指令的执行过程看成工厂加工产品的过程,当没有采用流水线时只有一个工人A,工人A先对指令取指,然后对指令译码,最后执行指令,然后再次对指令取指周而复始。工人A在同一个时间只能干一件事,指令的执行也就只有等工人A对指令完成取指和译码之后才能进行。工厂老板一看,这产品的生产效率太低,就又请了工人B和工人C,现在工人A只负责取指,工人B只负责译码,工人C只负责执行,这样三条流水线同时工作,每时每刻都有指令在被取指,译码和执行,产品的生产效率大大的提高了。三级流水线示意图如下图所示:上图是ARM指令集的三级流水线结构,每条指令的地址间隔为4字节,当CPU在t3时间段开始执行add r0,r1,#3指令时,PC的值为0x00000008,即PC此时指向cmp r0,#9指令处。记住PC存放的是取指地址,不是当前CPU运行地址。结合上图对于Thumb指令集的PC值分析也是比较简单的。采用多级流水线技术后,并没有加速单条指令的执行,每条指令的步骤并没有减少,只是多条指令的不同操作步骤同时执行,因而从总体上看加快了指令流速度,缩短了程序执行时间。
ARMv7-A 那些事 - 1.概述ARM公司与芯片ARM公司是一家知识产权(IP)供应商,它与一般的半导体公司最大的不同就是它不制造芯片并且不向终端用户出售芯片,而是通过转让设计方案,由合作伙伴生产出各具特色的芯片。ARM公司利用这种双赢的伙伴关系迅速成为了全球性RISC微处理器标准的缔造者。这种模式也给用户带来了巨大的好处,因为用户只需要掌握一种ARM内核结构及其开发手段,就能够使用多家公司相同ARM内核的芯片。ARM处理器在性能,成本与功耗之间的平衡,是ARM处理器的亮点。在智能家居、物联网、平板电脑、多媒体数字、汽车电子、医疗电子等领域ARM处理器具有统治地位。指令集、架构与处理器ARM体系结构是一种硬件规范,主要用来约定指令集、芯片内部体系结构(如MMU、Cache)等。指令集是处理器使用的指令编码方式,ARM指令集的命名方式为ARMv+version,目前是ARMv1~ARMv9,数字越大表示指令集越先进。下面列举一些指令集应用到具体处理器的例子。ARMv4和ARMv4T指令集主要在ARM7TDMI、ARM920T和StrongARM这些处理器中使用。ARMv5指令集主要在ARM926EJ-S、ARM946E-S和XScale这些处理器中使用。ARMv6指令集主要在ARM1136J-S、ARM1176JZ-S和ARM1156T2-S这些处理器中使用。ARMv6-M指令集主要在Cortex-M0和Cortex-M1这些处理器中使用。ARMv7-A指令集主要在Cortex-A5、Cortex-A7、Cortex-A8、Cortex-A9、Cortex-A12和Cortex-A15这些处理器中使用。ARMv7-R指令集主要在Cortex-R4、Cortex-R5和Cortex-R7这些处理器中使用。ARMv7-M指令集主要在Cortex-M3和Cortex-M4这些处理器中使用。架构主要是指某一个处理器所使用的具体指令集。在大部分场合,架构等于指令集。比如说i.MX 6ULL处理器是基于ARMv7-A架构的,也就是说i.MX 6ULL处理器使用的是ARMv7-A指令集。基于ARMv7-A的处理器内部结构ARMv7-A采用的是32位结构,因此其核心寄存器也是32位宽。基于ARMv7-A实现的处理器内部结构描述如下:处理器核心:有单核和多核之分,对称多核应用比较广泛,通常每个核心会包含L1 I-Cache、L1 D-Cache,可选的浮点单元,可选的NEON,MMU等。中断控制器:GIC。系统总线:处理器核心通过系统总线与外设控制器进行数据交互。时钟系统;电源管理系统;复位系统;调试系统。基于ARMv7-A指令集实现的Cortex-A5处理器的结构示意图如下所示:ARMv7-A的概述就先介绍这些吧,请关注ARMv7-A后续的内容。
CPU:STM32F429IGT6对于其他的stm32芯片或者其他ARM Cortex-M芯片,其实解决方法都相通。建议先完整阅读了本文之后,再对照着你所遇到问题的现象进行调试。1.基础知识在ARM Cortex-M系列处理器中,有若干个系统异常专用于 fault 处理。 CM3 中的 Faults 可分为以下几类:(1).总线 faults;(2).存储器管理 faults;(3).用法 faults;(4).硬 fault;1.1.总线 faults当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总线faults,产生的场合可以是:(1).取指,通常被称作“预取流产”(prefetch abort);(2).数据读/写,通常被称作“数据流产”(data abort);在 CM3 中,执行如下动作时,如果地址有误,亦会触发总线异常:(1).中断处理起始阶段的堆栈 PUSH 动作。此时若发生总线 fault,则称为“入栈错误”;(2).中断处理收尾阶段的堆栈 POP 动作。此时若发生总线 fault,则称为“出栈错误”;(3).在处理器启动中断服务序列(sequence)后读取向量时。这是一种极度罕见的特殊情况,被归类为硬 fault。总线 fault 状态寄存器(BFSR),地址:0xE000_ED29,BFSR的各个位的定义如下:1.2.存储器管理 faults存储器管理 faults 多与 MPU 有关,其诱因常常是某次访问触犯了 MPU 设置的保护规范。另外,某些非法访问,例如,在不可执行的存储器区域试图取指,也会触发一个 MemManage fault,而且在这种场合下,即使没有 MPU 也会触发 MemMange fault。MemManage faults 的常见诱因如下所示:(1).访问了所有 MPU regions 覆盖范围之外的地址;(2).访问了没有存储器与之对应的空地址;(3).往只读 region 写数据;(4).用户级下访问了只允许在特权级下访问的地址;存储器管理 fault 状态寄存器(MFSR),地址:0xE000_ED28,MFSR的各个位的定义如下:1.3.用法 faults用法 faults 发生的场合可以是:(1).执行了协处理器指令。 Cortex-M3 本身并不支持协处理器,但是通过 fault 异常机制,可以建立一套“软件模拟”的机制,来执行一段程序模拟协处理器的功能,从而可以方便地在其它 Cortex 处理器间移植。(2).执行了未定义的指令。同上一点的道理,亦可以软件模拟未定义指令的功能。(3).尝试进入 ARM 状态。因为 CM3 不支持 ARM 状态,所以用法 fault 会在切换时产生。软件可以利用此机制来测试某处理器是否支持 ARM 状态。(4).无效的中断返回(LR 中包含了无效/错误的值);(5).使用多重加载/存储指令时,地址没有对齐。另外,如果需要严格要求程序的质量,还可以让 CM3 在遇到除数为零的时候,以及遇到未对齐访问的时候也产生用法 fault。在 NVIC 中有两个控制位分别与它们对应。通过设置这两个控制位,就可以激活它们。用法 fault 状态寄存器(UFSR),地址:0xE000_ED2A,UFSR的各个位的定义如下:1.4.硬 fault硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。如果这些 fault 的服务例程无法执行,它们就会成为“硬伤” ——上访( escalation)成硬 fault。另外,在取向量(异常处理时对异常向量表的读取)时产生的总线 fault 也按硬 fault 处理。在 NVIC中有一个硬 fault 状态寄存器(HFSR),它指出产生硬 fault 的原因。如果不是由于取向量造成的,则硬 fault 服务例程必须检查其它的 fault 状态寄存器,以最终决定是谁上访的。硬 fault 状态寄存器(HFSR),地址:0xE000_ED2C,HFSR的各个位的定义如下:2.UsageFault INVPC置1解决过程最近在使用RTOS增加DMA驱动时,在对内存到设备和设备到内存的DMA传输测试时,出现了UsageFault,并且UFSR中的INVPC置1了。最开始,单独测试DMA发送是没有问题的,但是DMA发送和接收一起测试时,就会出现UsageFault(INVPC置1)。这个异常不太好定位出现问题的具体位置,因此就检查DMA驱动,并且逐步调试吧。最终,DMA驱动检查和修改好了,仍然出现UsageFault,实在没法了,还是从为什么会出现UsageFault(INVPC置1)开始分析吧。2.1.出现UsageFault(INVPC置1)的原因如果LR中的EXC_RETURN不是合法的值(合法值见下图,包括企图返回ARM状态),则引起用法fault。如果用法fault被除能,也上访成硬fault。此时,用法Fault状态寄存器(UFSR,地址: 0xE000_ED2A)中的INVPC位(位偏移: 2),或者是INVSTATE位(位偏移: 1)置位。上面就是出现该异常的文字分析了。2.2.UsageFault(INVPC置1)的解决过程因为该异常是异常响应期间才可能出现的异常(<<Cortex-M3权威指南>> 9.8节介绍了下,在9.8.4节进行文字说明),因此,只要在异常或中断的返回处打断点,执行下一步就有可能进入UsageFault异常。(当然了这个方法,是比较笨的,不过在缩小了异常出现的范围之后,可以在每次异常或中断的返回处打断点,然后执行下一步,就有可能进入UsageFault异常)我这里是每次开始DMA测试之后,就进入UsageFault异常,并且我的系统中目前就打开了SysTick,PendSV,DMA1_STREAM5,DMA1_STREAM6,NMI,HardFault,MemFault,BusFault,UsageFault这些异常和中断。因此我在开始DMA测试的时候打一个断点,在程序运行到DMA测试开始的断点处时,再在DMA1_STREAM5,DMA1_STREAM6,NMI,HardFault,MemFault,BusFault,UsageFault这些函数的入口打断点,在PendSV的返回处打断点,SysTick就暂时先不管。然后全力运行,发现每次都会在PendSV的异常返回断点处停留(因为任务切换嘛)总共在PendSV的断点处停留了大概7到8次,就进入到了UsageFault。有了上面步骤的铺垫,先去除中断和异常中的断点,还是先在DMA测试开始处打断点,等运行到DMA测试开始处,再在上述的中断和异常相关位置打断点。接下来我就慢慢的调试,在开始DMA测试之后,全速运行,在退出PendSV异常时,执行单步运行到下一步,重复7到8次,从PendSV就进入了UsageFault,在这7到8次中,我看在退出PendSV时,LR寄存器中的值都是0xFFFFFFFD,是合法的啊,当时仔细一想,有可能是退出异常时硬件再将堆栈中的PC赋值给PC时出问题,导致进入了UsageFault。果不其然,在PenSV退出之前,我查看每个PSP(0xFFFFFFFD:返回线程模式,并使用线程堆栈)对应内存数值,能够正常退出PendSV的寄存器和PSP堆栈内容如下图所示:进入UsageFault异常之前的寄存器和PSP堆栈内容如下图所示:此处堆栈中的内容,明显的0x20003D94堆栈中的PC值是有问题的,我的程序是烧写到flash中的PC地址应该是08xxxxxx,然而现在堆栈中的PC地址是0x20003DA8,这个地址是SRAM中的地址,SRAM存储的数据而不是代码,出现这个问题的原因,猜想一下,应该就是任务堆栈溢出导致,当我增加任务堆栈的大小之后,哈哈,程序正常运行,世界是如此美好。3.调试小结3.1.解决过程小结其实上面的步骤2,是我自己的一个调试解决问题的过程,这里给大家提供一个比较直接的解决方式。在开始运行程序之前,直接在UsageFault异常入口函数中打一个断点,然后全速运行程序,等程序停止在UsageFault异常函数的断点处时,需要注意以下几点:(1).如果LR是合法值,那么根据LR判断退出异常时使用的堆栈,然后在Memory查看窗口中,查看堆栈中R0,R1,R2,R3,R12,LR,PC,xPSR这些寄存器的值,根据这些寄存器的值,判断是否是堆栈溢出导致该异常发生;如果不是堆栈溢出导致该异常发生,那么就要根据PC值,在汇编窗口中跳转到PC值对应的代码处,分析导致异常发生的原因;(2).如果LR不是合法值,就要分析下你的代码中,有哪些地方修改过LR的值,确保修改的值要是合法的。3.2.关于UsageFault 如何才能让INVPC置1(1).在退出异常或中断时,执行BX LR时,LR的值是非法的,此时就会触发UsageFault异常,并且INVPC置1。(2).在退出异常或中断时,执行BX LR时,LR的值是合法的,但是退出异常之后要使用的堆栈中,堆栈里面的PC值是有问题的,此时就有可能触发UsageFault异常,并且INVPC置1。对于上面的两点的模拟其实也比较好做,在PendSV或者其他异常的退出的地方打一个断点,然后手动修改LR或者堆栈中PC的值,就能触发UsageFault异常,并且INVPC置1。这里注意一下,修改堆栈中PC的值,我这里测试时候,设置PC值为其他值可能引起其他的异常,貌似修改PC的值为RAM中数据区的地址才会出现该异常,不太清楚为什么会这样,可能是数据区是没有执行代码的权限,因此出异常吧,不太确定,有知道的朋友,欢迎留言讲解。当然了,博主的知识水平有限,对于异常的一些说明可能会有误,如果有误,欢迎指正,谢谢!
1.浮点运算指令浮点运算指令用于FPU单元的单精度浮点运算。浮点运算指令都是用V开头的汇编指令。只有在FPU开启的状态下,才能运行这些指令。如果在FPU没有开启的状态下,执行了浮点运算指令,系统会产生一个硬fault异常。2.为什么FPU需要Lazy Stacking当Cortex-M系列的芯片多了对浮点运算的支持之后,在中断响应和退出时会增加对FPU扩展寄存器的保护。入栈浮点寄存器组所带来的影响有如下几方面:a.增加stack frame所占的存储区域;b.增加中断响应延迟;c.在OS环境下,增加上下文切换时间;为了减少中断响应延迟和OS环境下的上下文切换时间,引入了FPU的Lazy Stacking机制。3.FPU Lazy Stacking简述Lazy Stacking机制在下面的情况下,会跳过对浮点寄存器组的入栈操作(仅预留浮点寄存器组S0~S15和FPSCR的存储空间),以避免中断延迟的增加:a.中断处理函数不使用FPU;b.被中断的程序未曾用到FPU;被中断函数使用了FPU,如果在执行中断处理函数时也用到了FPU,在执行到中断函数的第一条浮点指令时,内核会暂停,然后硬件自动将先前的浮点寄存器内容(S0~S15,FPSCR)压入预留的存储空间中。Lazy Stacking是可以通过软件使能和关闭的,如下述操作:a.使能Lazy Stacking必须同时置位FPCCR寄存器的LSPEN位和ASPEN位;b.清除FPCCR寄存器的LSPEN位可以关闭Lazy Stacking;关于FPCCR寄存器的LSPEN位和ASPEN位的组合情况有如下说明:FPCCR.ASPENFPCCR.LSPEN说明00取消自动状态保存。中断响应时不入栈FPU寄存器。应用场景:1. 应用中没有用到OS或者多任务调度,如果没有任何中断异常用到FPU。2. 在应用程序代码中只有一个中断用到FPU。如果有多个中断用到FPU,那么中断嵌套必须被禁止。可以通过把所有的中断优先级设置位相同优先级实现。10关闭Lazy Stacking,仅打开自动状态保存。如果用到FPU,CONTROL.FPCA位自动置1。中断响应时,硬件自动入栈S0~S15和FPSCR寄存器11打开Lazy Stacking,打开自动状态保存。如果用到FPU,CONTROL.FPCA位自动置1。如果响应中断时,CONTROL.FPCA为1,处理器在堆栈中预留FPU寄存器的空间,同时将FPCCR.LSPACT位置1。但是PFU寄存器并没有马上入栈,直到在中断处理函数中用到FPU时再入栈。01非法配置4.FPU Lazy Stacking用到的几个重要标志浮点寄存器入栈和出栈过程中用到的标志位:标志位说明CONTROL.FPCA0 = 当前上下文中没有用到FPU1 = 当前上下文中用到FPU这里的上下文,即表示普通的任务上下文,也表示中断上下文。FPCCR.LSPACT0 = 退出Lazy状态(实际入栈后被硬件清零或者中断返回时硬件清零)1 = 进入Lazy状态(栈帧中预留了FPU寄存器的空间,但没有实际入栈)EXC_RETURN[4]0 = 栈帧中包括了FPU寄存器空间1 = 栈帧中不包括FPU寄存器空间5.FPU Lazy Stacking使用实例下面的例子都是在使能了FPU的Lazy Stacking机制下进行说明的。(1).被中断的程序和中断程序中都没有使用到FPU,其示意图如下所示:(2).被中断的程序用到了FPU,中断程序中没有使用到FPU,其示意图如下所示:(3).被中断的程序和中断程序中都用到了FPU,其示意图如下所示:6.RTOS使用Lazy Stacking首先肯定是要使能FPU的Lazy Stacking,然后在PendSV切换任务的时候做下面的处理:(1).上文保存阶段:检测 EXC_RETURN 的 bit4(通过LR访问),如果该位为零,就入栈剩下的S16-S31 即可;如果该位为 1,则无需保存 FPU 寄存器,因为该任务未曾使用过 FPU。当然了这里有一个标志数据,表示当前任务是否使用了FPU,便于下次任务恢复时,确定是否需要从堆栈弹出内容到S16-S31。标志数据也压入当前任务的堆栈进行保存。(2).下文恢复阶段:从堆栈中弹出标志数据,判断待恢复的任务是否使用了FPU,如果使用了FPU,那么从堆栈中弹出内容到S16-S31;如果没有使用FPU,则不需要恢复FPU寄存器。接下来需要修改当前LR寄存器的内容,如果使用了FPU,LR(EXC_RETURN)的bit4需要清零,来保证在退出PendSV时将先前压入该任务堆栈的S0-S15和FPSCR寄存器出栈;如果没有使用FPU,LR(EXC_RETURN)的bit4需要置1,来告诉CPU该任务栈帧中不包括FPU寄存器内容,不需要进行出栈操作。对于S0-S15和FPSCR寄存器,如果任务使用了FPU,在进入到PendSV之前,就在任务堆栈中预留了存储空间,当执行上文保存阶段的S16-S31的入栈操作时,会首先将S0-S15和FPSCR寄存器压入先前堆栈预留的存储空间中,接着再将S16-S31压入堆栈。如果这篇文章对你有帮助,记得点赞和关注博主就行了^_^。
- 1
本站信息
目前本站共被浏览 165576 次
目前本站已经运行 3577 天
目前本站共有 165 篇文章
目前本站共有 6 条评论信息
目前本站共有 104 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2023年11月26日
目前本站已经运行 3577 天
目前本站共有 165 篇文章
目前本站共有 6 条评论信息
目前本站共有 104 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2023年11月26日
JLink V9掉固件修复(灯不亮) 3Zephyr笔记2 - 在STM32F429上运行HelloWorld 2计算NandFlash要传入的行地址和列地址 1Linux MMC子系统 - 6.eMMC 5.1工作模式-设备识别模式 0Linux MMC子系统 - 5.eMMC 5.1工作模式-引导模式 0Linux MMC子系统 - 4.eMMC 5.1常用命令说明(2) 0
最新评论
标签云
Linux嵌入式实用技巧ARM内核学习问题集合CC++编程语言阅读笔记汇编Linux内核完全注释Windows驱动开发计算机基础ARM11ARMv7-ASTM32IDESublimeLinux内核学习eMMCMMC子系统Ubuntu操作系统OfficeVMWareAPUEgccRTOS中断漫游世界随笔感悟开发工具软件应用编程VsCodearmccarmclang编译器ZephyrSPIJLink网卡驱动安装各种芯片库函数NFSμCOS内核sambaFlashUnix命令与脚本输入法Linux内核设计与实现gitRIFFWAVJATGFTPar8161安装centos有线上网μCGUI字库工程建立右键菜单网络文件系统Firefox百度NTFS文件系统CodeBlocksCentOS数据结构算法PhotoShop51KeilQTUltraEditscanfglibc宏定义UIDGID优先级娱乐天地SourceInsight磁盘扇区总线I2CPDFBComparePythonI2SFPUMakefileSWDCPUARP软件推荐FileZilla