一.Fristly,我们现在来看寻址的概念以及八大寻址方式
(1)什么是寻址方式?
所谓寻址方式就是处理器根据指令中给出的地址信息来寻找操作数物理地址的方式。
(2)有哪些寻址方式:
1.立即数寻址
立即寻址是一种特殊的寻址方式,指令中在操作码字段后面的地址码部分不是操作数地址,而是操作数本身。立即数要以 “#” 号作前缀,以十进制数 10 为例:它的 16 进制立即数为 #0xa ;它的 2 进制立即数为 #0b1010例:2.寄存器寻址
寄存器寻址是指所需要的值在寄存器中,指令中地址码给出的是寄存器编号,即寄存器的内容为操作数。
例:
ADD R0, R1, R2 ;R0←R1+R2
MOV R2 , R0 ; R2 ← R0CMP R7 , R8 ;比较 R7 和 R8 的值来个小例子:.text ldr r0, =buf @ ldr 从地址上取 4字节 @ [r0, #12] 表示 r0 + 12, 12表示12个字节 @ ldr r1, [r0, #12] mov r10, #1 loop: cmp r10, #5 ldrle r1, [r0], #4 addle r10, #1 ble loop NOP .data buf: .word 5, 3, 1, 4, 2 .end
3.寄存器移位寻址
寄存器移位寻址方式是ARM指令集中所特有的,第二个寄存器操作数在与第一个操作数结合之前,选择进行移位操作。在寄存器移位寻址中,移位的位数可以用立即数或寄存器方式表示。
例 : ADD R3, R2, R1, LSL #3MOV R0,R1,ROR R2注意: ARM 中有一桶式移位器,途经它的操作数在被使用前能够被移位或循环移位任意位数,这在处理列表、表格和其他复杂数据结构时非常有用。ARM中常用的几种移位操作指令:
(1)逻辑左移LSL
(2)逻辑右移LSR
(3)算术右移ASR
(4)循环右移ROR
(5)扩展的循环右移RRX
(1)逻辑左移LSL
存储第二操作数的寄存器逻辑左移。寄存器中的高端送至C标志位,低端空出位补0。(乘以2的n次方)
(2)逻辑右移LSR
存储第二操作数的寄存器逻辑右移。寄存器中的高端空出位补0。(除以2的n次方)
(3)算术右移ASR
存储第二操作数的寄存器算术右移。算术移位的操作数是带符号数,完成移位时应该保持操作数的符号不变。因此,当被移位的操作数为正数时,寄存器的高端空出位补0;当被移位的操作数为负数时,寄存器的高端空出位补1。
(4)循环右移ROR
存储第二操作数的寄存器循环右移。从寄存器低端移出的位填入到寄存器高端的空出位上。
(5)扩展的循环右移RRX
存储第二操作数的寄存器进行带进位位的循环右移。每右移一位,寄存器中 高端空出位用原 C 标志位的值填充 。若移位的位数由 5 位立即数 ( 取值范围 0-31) 给出,就叫作立即数控制移位方式 (immediate specified shift) ;若移位的位数由通用寄存器 ( 不能是 R15) 的低 5 位决定,就叫做寄存器控制移位方式 (register specified shift) 。4. 寄存器间接寻址
寄存器间接寻址是指指令中的地址码给出的是某一通用寄存器的编号,在被指定的寄存器中存放操作数的有效地址,而操作数则存放在该地址对应的存储单元中,即寄存器为地址指针。
指令格式:LDR|STR{
}{B}{T} ,[Rm] 例:LDR R0, [R1] ; R0←[R1]5.基址变址寻址
变址寻址 ( 或基址变址寻址 ) 就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数有效地址。变址寻址用于访问基址附近的单元,包括基址加偏移和基址加索引寻址。寄存器间接寻址是偏移量为 0 的基址加偏移寻址。基址加偏移寻址中的基址寄存器包含的不是确切的地址。基址需加(或减)最大 4KB 的偏移来计算访问的地址。指令格式:LDR|STR{}{B}{T} ,[Rm,± ] 3 种加偏移量 ( 偏移地址 ) 的变址寻址方式:( 1 )前变址方式 (pre-indexed)先将基地址加上偏移量,生成操作数地址,再做指令指定的操作。该方式不修改基址寄存器。( 2 )自动变址方式 ( auto-indexed)先将基地址加上偏移量,生成操作数地址,再做指令指定的操作;然后再自动修改基址寄存器。例: LDR R0, [R1, #4]! ; R0←[R1 + 4], R1←R1 + 4说明:!表示完成数据传输后要更新基址寄存器。
(3)后变址方式(post-indexed)
先将基址寄存器作为操作数地址;完成指令操作后,再将基地址加上偏移量修改基址寄存器。即先用基地址传数,然后再修改基地址 ( 基址 + 偏移 ) 。例:STR R0, [R1], #12 ; [R1]←R0, R1←R1 + 12这里 R1 是基址寄存器。6.多寄存器寻址
多寄存器寻址是指一条指令可以传送多个寄存器的值,允许一条指令传送16个寄存器的任何子集。
例 :LDMIA R1, {R0, R2, R5} ; R0←[R1], R2←[R1+4], R5←[R1+8]上面指令的含义是将 R1 所指向的连续 3 个存储单元中的内容分别送到寄存器 R0,R2,R5 中。由于传送的数据项总是32 位的字,基址 R1 应该字对准。7.块拷贝寻址
块拷贝寻址是指把存储器中的一个数据块加载到多个寄存器中,或者是把多个寄存器中的内容保存到存储器中。●块拷贝寻址是多寄存器传送指令LDM/STM的寻址方式,因此也叫多寄存器寻址;
●多寄存器传送指令用于把一块数据从存储器的某一位置拷贝到另一位置;
●块拷贝指令的寻址操作取决于数据是存储在基址寄存器所指的地址之上还是之下、地址是递增还是递减,并与数据的存取操作有关;
●块拷贝寻址操作中的寄存器,可以是R0~R15这16个寄存器的子集,或是所有寄存器。
8.堆栈操作寻址
堆栈是一种按特定顺序进行存取的存储区,这种特定顺序即是“先进后出”或“后进先出”。堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向一块存储器区域。栈指针所指定的存储单元就是堆栈的栈顶。按照 地址增长方向 堆栈可分为两种:●向上生长:又称递增 (Ascending) 堆栈,即地址向高地址方向生长。● 向下生长:又称递减 ( Decending ) 堆栈,即地址向低地址方向生长。按照 栈指针指向元素的属性 可分为两种:●Full Stack :又称满栈,即 SP 指向最后压入的堆栈的有效数据单元。● Empty stack :又称空栈,即 SP 指向下一个数据项放入的空单元。ARM处理器支持上面四种类型的堆栈工作方式:●满递增堆栈FA(Full Ascending) :堆栈指针指向最后压入的数据单元,且由低地址向高地址生成;●满递减堆栈 FD(Full Descending) :堆栈指针指向最后压入的数据单元,且由高地址向低地址生成;●空递增堆栈 EA(Empty Ascending) :堆栈指针指向下一个将要放入数据的空单元,且由低地址向高地址生成;●空递减堆栈 ED(Empty Descending) :堆栈指针指向下一个将要放入数据的空单元,且由高地址向低地址生成;
二.异常中断
(1)中断的概念
硬中断: 在处理器中,CPU在正常执行程序的过程中,遇到外部/内部的紧急事件需要处理,暂时中断(中止)当前程序的执行,而转去为事件服务,待服务完毕,再返回到暂停处(断点)继续执行原来的程序,这可称为硬中断。
软中断:事先在程序中安排特殊的指令,CPU执行到该类指令时,转去执行相应的一段预先安排好的程序,然后再返回去执行原来的程序,这可称为软中断。
(2)中断源
引起中断的信号源称为中断源。
(3)中断优先级的概念
ARM处理器中有7种类型的异常,按优先级从高到低的顺序排列如下:
(1)复位异常(Reset)
(2)数据异常(Data Abort)
(3)快速中断异常(FIQ)
(4)外部中断异常(IRQ)
(5)预取异常( Prefetch Abort)
(6)软件中断(SWI)
(7)未定义指令异常(UndefinedInstruction )
ARM处理器模式和异常
每一种异常都会导致内核进入一种特定的模式,如表所示。此外,也可以通过编程改变CPSR,进入任何一种ARM处理器模式。
用户和系统模式是仅有的不可通过异常进入的两种模式,要进入这两种模式,必须通过编程改变CPSR。
ARM异常响应和处理程序返回
中断响应可以分为以下几个步骤:
(1)保护断点。即保存下一将要执行的指令的地址,也就是把这个地址送入堆栈。
(2)寻找中断入口。根据不同的中断源所产生的中断,查找不同的入口地址。
(3)执行中断处理程序。
(4)中断返回。执行完中断指令后,就从中断处返回到主程序,继续执行。
三.异常向量表
(一)异常向量表作用:
因为异常的产生是随机的,每一种异常都需要处理,因此厂家规定一个固定的区域,用来计划异常的跳转。
(二)向量表:
跳转指令B的跳转范围为±32MB。当需要更大范围的跳转,而且由于向量表空间的限制,只能由一条指令完成。具体实现方法有下面两种:
(1)MOV PC,#imme_value。这种办法将目标地址直接赋值给PC。但这种方法受格式限制不能处理任意立即数。
(2)LDR PC,[PC+offset]。把目标地址先存储在某一个合适的地址空间,然后把这个存储器单元的32位数据传送给PC来实现跳转。这种方法对目标地址值没有要求。
(三)从异常处理程序重返回
当一个ARM异常处理返回时,一共有3件事情需要处理:
(1)通用寄存器的恢复;
(2)状态寄存器的恢复;
(3) PC指针的恢复
1、通用寄存器的恢复:采用一般的堆栈操作指令即可。
2、状态寄存器的恢复和PC指针的恢复:
(1)恢复被中断程序的处理器状态
PC和CPSR的恢复可以通过一条指令来实现:
这几条指令是普通的数据处理指令,特殊之处在于它们把程序计数器寄存器PC作为目标寄存器,并且带了特殊的后缀“S”或“^”。
(2)异常的返回地址
----(1)软中断异常。
如果发生软中断异常,即指令B为SWI指令,从SWI中断返回后下一条执行指令就是C,正好是LR寄存器保存的地址,所以只要直接把LR恢复给PC即可。
----(2)IRQ或FIQ异常。
如果发生的是IRQ或FIQ异常,因为外部中断请求中断了正在执行的指令B,当中断返回后,需要重新回到B指令执行,也就是说,返回地址应该是B(0x8004),需要把LR减4送PC。
----(3)DataAbort数据中止异常。
在指令B处进入数据异常的响应,但导致数据异常的原因却是上一条指令A。当中断处理程序恢复数据异常后,要回到A重新执行导致数据异常的指令,因此返回地址应该是LR减8。
IRQ中断完整处理流程
ARM的SWI异常中断处理程序设计
1.判断 SWI 中断号
当发生 SWI 异常,进入异常处理程序时,异常处理程序必须提取 SWI 中断号,
从而得到用户请求的特定 SWI 功能。
在 SWI 指令的编码格式中,后 24 位称为指令的“comment field”。该域保存
的 24 位数即为 SWI 指令的中断号,如下图所示。
第一级的 SWI 处理函数通过 LR 寄存器的内容得到 SWI 指令地址,并从存储器中得到 SWI 指令编码。这些工作通过汇编语言、嵌入型汇编来完成。
例子中,使用 LR-4 得到 SWI 指令的地址,再通过“BIC r0, r0, #0xFF000000”指令提取 SWI 指令中断号。
2.使用 C 语言编写 SWI 异常处理函数
虽然第一级 SWI 处理函数(完成中断向量号的提取)必须用汇编语言完成,
但第二级中断处理函数(根据提取的中断向量号,跳转到具体处理函数)就可以使
用 C 语言来完成。
第一级的中断处理函数已经将中断号提取到寄存器 r0 中,所以根据AAPCS 函数调用规则,可以直接使用 BL 指令跳转到 C 语言函数,而且中断向量号作为第一个参数被传递到 C 函数中。
最后依然来一段例子:
.text _start: @ 上电时,cpu默认在异常向量表的 0地址处取址 b reset @ 0x00 上电复位异常 SVC NOP @ 0x04 未定义指令异常 UNDEF ldr pc,_swi_handler @ 0x08 软中断异常 SVC NOP @ 0x0c 预取指令异常 abort NOP @ 0x10 数据异常 abort NOP @ 0x14 保留位 NOP @ 0x18 IRQ低优先级中断异常 IRQ NOP @ 0x1c FIQ高优先级中断异常 FIQ _swi_handler: @ 异常处理函数往往不在当前文件, @ 为了突破当前文件32M的空间限制 .long swi_handler @ 进行异常的相关处理 swi_handler: @ 进栈,保护现场 stmfd sp!, {r0-r12, lr} @ swi机器码,swi指令所在地址为 lr-4 ldr r0, [lr, #-4] @ 取出机器码中的中断号 0~23 and r0, r0, #0xffffff cmp r0, #1 @ bleq .... cmp r0, #2 @ bleq .... cmp r0, #3 @ bleq .... @ 出栈,恢复现场, ^自动恢复spsr到cpsr ldmfd sp!, {r0-r12, pc}^ reset: ldr sp, =stack_top @ 将svc切换成 usr模式,模拟用户程序 mrs r0, cpsr bic r0, #0x3 msr cpsr, r0 NOP @ swi软中断: @ 1、cpu会将cpsr自动保存在spsr寄存器中, @ 2、处理器由 usr 模式 进入svc模式, @ 3、lr 保存下条指令的地址 swi 1 NOP swi 2 NOP swi 3 NOP .data buf: .space 125*4 stack_top: .end
大家有兴趣可以在keil上进行单步执行一下看一下运行的效果状态。