ARM-寄存器祥解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
ARM的汇编编程,本质上就是针对CPU寄存器的编程,所以我们首先要弄清楚ARM有哪些寄存器?这些寄存器都是如何使用的?
ARM处理器共有7种运行模式:
用户模式(user,use)正常程序执行模式
快速中断模式(fiq)用于高速数据传输和通道处理
外部中断模式(irq)用于通常的中断处理
管理模式(sve)操作系统使用的一种保护模式
数据访问终止模式(abt)用于虚拟存储及存储保护
未定义指令终止模式(und)用于支持通过软件方针硬件的协处理器
系统模式(sys)用于运行特权级的操作系统任务
除了用户模式之外的6种处理器模式称为特权模式(privileged modes)。
在这些模式下,程序可以访问所有的系统资源,也可以任意的进行处理器模式切换。
其中,除系统模式外,其他5种特权模式又称为异常模式。
处理器模式可以通过软件控制进行切换,也可以通过外部中断或异常处理过程进行切换。
大多数的用户程序运行在用户模式下。
这时,应用程序不能够访问一些受操作系统保护的系统资源。
应用程序也不能直接进行处理器模式的切换。
当需要进行处理器模式切换时,应用程序可以产生异常处理,在异常处理过程中进行处理器模式的切换。
这种体系结构可以使系统控制整个系统的资源。
当应用程序发生异常中断时,处理器进入相应的异常模式。
在每一种异常模式中都有一组寄存器,供相应的异常处理程序使用,这样就可以保证在进入异常模式时,用户模式下的寄存器(保存了程序运行状态)不被破坏。
系统模式并不是通过异常过程进入的,它和用户模式具有完全一样的寄存器。
但是系统模式属于特权模式,可以访问所有的系统资源,也可以直接进行处理器模式切换。
它主要供操作系统任务使用。
通常操作系统的任务需要访问所有的系统资源,同时该任务仍然使用用户模式的寄存器组,而不是使用异常模式下相应的寄存器组,这样可以保证当异常中断发生时任务状态不被破坏。
ARM寄存器分为2类,普通寄存器和状态寄存器
请看上表的第2列,普通寄存器总共16个,分别为R0-R15;状态寄存器共2个,分别为CPSR和SPSR
1.通用寄存器
通常又可以分为下面3类。
n 未备份寄存器:包括R0~R7。
n 备份寄存器:包括R8~R14。
n 程序计数器PC:即R15。
1)未备份寄存器R0~R7
对于每个未备份寄存器来说,在所有的处理器模式下指的都是同一个物理寄存器,在异常中断造成处理器模式切换时,由于不同的处理器模式使用相同的物理寄存器,可能造成寄存器中数据被破坏。
未备份寄存器没有被系统用于特别的用途,任何可采用通用寄存器的应用场合都可以使用未备份寄存器。
2)备份寄存器R8~R14
备份寄存器R8~R12中的每个寄存器对应于两个不同的物理寄存器。
例如,当使用快速中断模式下的寄存器时,寄存器R8和寄存器R9分别记做R8_fiq和 R9_fiq,当使用用户模式下的寄存器时,寄存器R8和寄存器R9分别记做R8_usr和R9_usr等。
在这两种情况下使用的是不同的物理寄存器,系统没有将这几个寄存器用于任何的特殊用途。
中断处理非常简单,仅仅使用R8~R14寄存器时,FIQ 处理程序可以不必执行保存和恢复中断现场的指令,从而可以使中断处理过程很迅速。
对于备份寄存器R13、R14来说,每个寄存器对应于6个不同的物理寄存器,其中的一个是用户模式和系统模式共用的,另外的5个则对应于其他5种处理器模式,采用下面的方法来标识。
R13_<mode>,
其中<mode>是usr、svc、abt、und、irq和fiq的一种。
R13通常用做堆栈指针(SP)。
每一种模式都拥有自己的物理R13。
程序初始化R13,使其指向该模式专用的栈地址。
当进入该模式时,可以将需要使用的寄存器保存在R13所指的栈中,当退出该模式时,将保存在R13所指的栈中的寄存器值弹出。
这样就实现了程序的现场保护。
寄存器R14又被称为连接寄存器(LR),在ARM中有下面两种特殊用途。
① 每一种处理器模式在自己的物理R14中存放当前子程序的返回地址。
当通过BL或者BLX指令调用子程序时,R14被设置成该子程序的返回地址。
在子程序中,当把R14的值复制到程序计数器PC中时,就实现了子程序返回。
可以通过下面两种方式实现这种子程序的返回操作。
◆执行下面任何一条指令
MOV pc, LR
BX LR
◆在子程序入口使用下面指令将PC保存到栈中:
STMFD SP!, {registers}, LR}
相应地,下面的指令可以实现子程序的返回:
LDMFD SP!, { registers}, LR }
② 当发生异常中断的时候,该模式下的特定物理R14被设置成该异常模式将要返回的地址。
对于某些异常,R14的值可能与将返回的地址有一个常数的偏移量。
具体的返回方式与上面的子程序返回方式基本相同。
普通寄存器中特别要提出来的是R13、R14、R15。
R15别名PC(program counter),中文称为程序计数器,它的值是当前正在执行的指令在内存中的位置(不考虑流水线的影响,参见流水线对PC值的影响一文),而当指令执行结束后,CPU硬件会自动将PC的值加上一个单位,从而使得PC的值为下一条即将执行的指令在内存中的位置,这样CPU硬件就可以根据PC的值自动完成取指的操作。
正是由于有PC的存在,以及CPU硬件会自动增加PC的值,并根据PC的值完成取指操作,才使得CPU一旦上电就永不停歇地运转,由此可见PC寄存器对于计算机的重要性。
对于我们进行汇编程序编写而言,PC寄存器亦是十分重要,因为当程序员通过汇编指令完成了对PC寄存器的赋值操作的时候,其实就是完成了一次无条件跳转,这一点非常重要,请务必要牢记。
R14别名LR(linked register),中文称为链接寄存器,它与子程序调用密切相关,用于存放子程序的返回地址,它是ARM程序实现子程序调用的关键所在。
下面我们用C语言中对子程序调用的实现细节来说明LR是如何被使用的。
1 int main(void)
2 {
3 int k, i = 1, j = 2;
4 addsub(i, j);
5 k = 3;
6 }
7 int addsub(int a, int b)
8 {
9 int c;
10 c = a + b;
11 return c;
12 }
对于上面的程序,编译器会将第4行编译为指令:BL addsub,将第11行编译为指令:MOV pc, lr。
(关于BL和MOV指令详见“基本寻址模式与基本指令”)
在这里,关键指令BL addsub会完成2件事情:1、将子程序的返回地址(也就是第5行代码在内存中的位置)保存到寄存器LR中;2、跳转到子程序addsub的第1条指令处。
这样就完成了子程序的调用。
而指令MOV pc, lr 则将保存在lr中的返回地址赋给pc,这样就完成了从子程序的返回。
由此可见,lr是用于存放子程序的返回地址的。
另外一个要引起注意的问题是,如果子程序又调用了孙子程序,那么根据前面的分析,在调用孙子程序时,lr寄存器中的值将从子程序的返回地址变为孙子程序的返回地址,这将导致从孙子程序返回子程序没有问题,但从子程序返回父程序则会出错。
那么这个问题如何解决呢?其实,如果我们编写的是C程序,那么我们一点也不用担心,因为编译器会为我们考虑一切,针对这个问题,编译器会在孙子程序的入口处增加入栈操作将lr的值入栈,然后在孙子程序的返回处增加出栈操作,将lr的值恢复,从而解决这个难题。
不过我们一定要保持头脑的清醒,因为你要知道,我们现在是在编写汇编子程序,此时编译器已经不能在这方面给我们提供保障,所以当你在编写汇编子程序的时候,发现该子程序还要再调用孙子程序,那么请你务必记住,一定要在子程序的入口处保存lr寄存器的值。
R13又名SP(stack pointer),中文名称栈指针寄存器。
顾名思义,它是用于存放堆栈的栈顶地址的。
也就是说,每次当我们进行出栈和入栈的时候,都将根据该寄存器的值来决定访问内存的位置(即:出入栈的内存位置),同时在出栈和入栈操作完成后,SP寄存器的值也应该相应增加或减少。
这里要特别说明的是,其实在32位的ARM指令集中没有专门的入栈指令和出栈指令,所以并不是一定要用SP来作为栈指针寄存器,除了PC外,任何普通寄存器均可作为栈指针寄存器,只不过,约定俗成都使用SP罢了。
我们将在“其它寻址模式与其它指令”一文中见到ARM中使用SP作为栈指针寄存器的出入栈指令。
寄存器R0-R12是普通的数据寄存器,可用于任何地方。
在不涉及ATPCS规则(在“ATPCS与混合编程”一文中详细介绍)的情况下,他们并没有什么特别的用法。
状态寄存器CPSR(current program status register),中文名称:当前程序状态寄存器,顾名思义它是用于保存程序的当前状态的。
那么,程序的哪些状态是需要保存的呢?
上图是CPSR寄存器的内容,主要由以下部分组成:
1、条件代码标志位。
它们是ARM指令条件执行的依据。
N:运算结果的最高位反映在该标志位。
对于有符号二进制补码,结果为负数时N=1,结果为正数或零时N=0;
Z:指令结果为0时Z=1(通常表示比较结果“相等”),否则Z=0;
C:当进行加法运算(包括CMN指令),并且最高位产生进位时C=1,否则C=0。
当进行减法运算(包括CMP 指令),并且最高位产生借位时C=0,否则C=1。
对于结合移位操作的非加法/减法指令,C为从最高位最后移出的值,其它指令C通常不变
V:当进行加法/减法运算,并且发生有符号溢出时V=1,否则V=0,其它指令V通常不变
2、控制位。
它们将控制CPU是否响应中断。
I:中断禁止位,当I位置位时,IRQ中断被禁止
F:快中断禁止位,当F位置位时,FIQ中断被禁止
T:反映了CPU当前的状态。
当T位置1时,处理器正在Thumb状态下运行;当T位清零时,处理器正在ARM状态下运行
3、模式位
包括M4、M3、M2、M1和M0,这些位决定了处理器的模式。
总共有7种模式:用户、快中断、中断、管理、中止、未定义、系统,分别会用于不同的情况和异常。
由此可见,不是所有模式位的组合都定义了有效的处理器模式,如果使用了错误的设置,将引起一个无法恢复的错误。
SPSR(saved program status register),中文名称:保存的程序状态寄存器
该寄存器的结构与CPSR完全一样,在异常发生时,由硬件自动将异常发生前的CPSR的值存放到SPSR中,以便将来在异常处理结束后,程序能恢复原来CPSR的值
1. LDR 指令
LDR 指令的语法为:
LDR{条件} 目的寄存器,<内存地址>
LDR 指令用于从内存中将一个32 位的字数据传送到目的寄存器中。
该指令通常用于从内存中读取32 位的字数据到通用寄存器,然后对数据进行处理。
当程序计数器PC 作为目的寄存器时,指令从内存中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
该指令在程序设计中比较常用,且寻址方式灵活多样,请读者认真掌握。
指令范例:
LDR R0,[R1] ;将内存地址为R1 的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将内存地址为R1+R2 的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将内存地址为R1+8 的字数据读入寄存器R0。
LDR R0,[R1,R2] !;将内存地址为R1+R2 的字数据读入寄存器R0,并将新地址R1+R2 写入R1。
LDR R0,[R1,#8] !;将内存地址为R1+8 的字数据读入寄存器R0,并将新地址R1+8 写入R1。
LDR R0,[R1],R2 ;将内存地址为R1 的字数据读入寄存器R0,并将新位址R1+R2 写入R1。
LDR R0,[R1,R2,LSL#2]!;将内存地址为R1+R2×4 的字数据读入寄存器R0,并将新地址R1+R2×4 写入R1。
LDR R0,[R1],R2,LSL#2 ;将内存地址为R1 的字数据读入寄存器R0,并将新位址R1+R2×4 写入R1。
2. LDRB 指令
LDRB 指令的语法为:
LDR{条件}B 目的寄存器,<内存地址>
LDRB 指令用于从内存中将一个8 位的字节数据传送到目的寄存器中,同时将寄存器的高24 位清零。
该指令通常用于从内存中读取8 位的位元组数据到通用寄存器,然后对数据进行处理。
当程序计数器PC 作为目的寄存器时,指令从内存中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
指令范例:
LDRB R0,[R1] ;将内存地址为R1 的字节数据读入寄存器R0,并将R0的高24 位清零。
LDRB R0,[R1,#8] ;将内存地址为R1+8 的字节数据读入寄存器R0,并将R0 的高24 位清零。
3. LDRH 指令
LDRH 指令的语法为:
LDR{条件}H 目的寄存器,<内存地址>
LDRH 指令用于从内存中将一个16 位的半字数据传送到目的寄存器40
中,同时将寄存器的高16 位清零。
该指令通常用于从内存中读取16 位的半字数据到通用寄存器,然后对数据进行处理。
当程序计数器PC 作为目的寄存器时,指令从内存中读取的字数据被当作目的地址,从而可以实现程序流程的跳移。
指令范例:
LDRH R0,[R1] ;将内存地址为R1 的半字数据读入寄存器R0,并将R0 的高16 位清零。
LDRH R0,[R1,#8] ;将内存地址为R1+8 的半字数据读入寄存器R0,并将R0的高16 位清零。
LDRH R0,[R1,R2] ;将内存地址为R1+R2 的半字数据读入寄存器R0,并将R0 的高16 位清零。
4. STR 指令
STR 指令的语法为:
STR{条件} 来源寄存器,<内存地址>
STR 指令用于从来源寄存器中将一个32 位的字数据传送到内存中。
该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
指令范例:
STR R0,[R1],#8 ;将R0 中的字数据写入以R1 为地址的内存中,并将新地址R1+8 写入R1。
STR R0,[R1,#8] ;将R0 中的字数据写入以R1+8 为地址的内存中。
5. STRB 指令
STRB 指令的语法为:
STR{条件}B 来源寄存器,<内存地址>
STRB 指令用于从来源寄存器中将一个8 位的字节数据传送到内存中。
该字节数据为来源寄存器中的低8 位。
指令范例:
STRB R0,[R1] ;将寄存器R0 中的字节数据写入以R1 为地址的内存中。
STRB R0,[R1,#8] ;将寄存器R0 中的字节数据写入以R1+8 为地址的内存中。
6. STRH 指令
STRH 指令的语法为:
STR{条件}H 来源寄存器,<内存地址>
STRH 指令用于从来源寄存器中将一个16 位的半字数据传送到内存中。
该半字数据为来源寄存器中的低16 位。
指令范例:
STRH R0,[R1] ;将寄存器R0 中的半字数据写入以R1 为地址的内存中。
STRH R0,[R1,#8] ;将寄存器R0 中的半字数据写入以R1+8 为地址的内存中。
2.6.6连续数据加载/存储指令
ARM 微处理器所支持连续数据加载/存储指令可以一次在一片连续的记忆体单元和多个寄存器之间传送数据,连续加载指令用于将一片连续的内存中的数据传送到多个寄存器,连续数据存储指令则完成相反的操作。
常用的载入存储指令如下:
LDM 连续数据加载指令
STM 连续数据存储指令
LDM(或STM)指令
LDM(或STM)指令的语法为:
LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或STM)指令用于从由基址寄存器所指示的一片连续内存到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入堆栈或出堆栈。
其中,{类型}为以下几种情况:
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;42
FA 满递增堆栈;
EA 空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0∼R15 的任意组合。
{∧}为可选后缀,当指令为LDM 且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR 复制到CPSR。
同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
指令范例:
STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4 到R12,LR)存入堆栈。
LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4 到R12,LR)。
2.6.7 数据交换指令
ARM 微处理器所支持数据交换指令能在内存和寄存器之间交换数据。
资料交换指令有如下两条:
SWP 字数据交换指令
SWPB 字节数据交换指令
SWP 指令
SWP 指令的语法为:
SWP{条件} 目的寄存器,来源寄存器1,[来源寄存器2]
SWP 指令用于将来源寄存器2 所指向的内存中的字数据传送到目的寄存器中,同时将来源寄存器1 中的字数据传送到来源寄存器2 所指向的内存中。
显然,当来源寄存器1 和目的寄存器为同一个寄存器时,指令交换该寄存器和记忆体的内容。
指令范例:
SWP R0,R1,[R2] ;将R2 所指向的内存中的字数据传送到R0,同时将R1 中的字数据传送到R2 所指向的存储单元。
SWP R0,R0,[R1] ;该指令完成将R1 所指向的内存中的字数据与R0 中的字资料交换。
SWPB 指令
SWPB 指令的语法为:
SWP{条件}B 目的寄存器,来源寄存器1,[来源寄存器2]
SWPB指令用于将来源寄存器2 所指向的内存中的字节数据传送到目的寄存器中,目的寄存器的高24 清零,同时将来源寄存器1 中的字节数据传送到来源寄存器2 所指向的内存中。
显然,当来源寄存器1 和目的寄存器为同一个寄存器时,指令交换该寄存器和内存的内容。
指令范例:
SWPB R0,R1,[R2] ;将R2 所指向的内存中的字节数据传送到R0,R0 的高24位清零,同时将R1 中的低8 位数据传送到R2 所指向的存储单元。
SWPB R0,R0,[R1] ;该指令完成将R1 所指向的内存中的字节数据与R0 中的低8位数据交换。