5.ARM/Thumb Unified Assembly Language Instructions
本章是对ARM/Thumb汇编语言的总体介绍,并未对每一条指令进行详细说明,单个指令的描述可以在附录A“指令概要”中找到。
指令大致可以归为以下几类:
数据处理操作(例如ADD等ALU操作)。
内存访问(对内存的加载和存储)。
控制流(用于循环、goto、条件代码和其他程序流控制)。
系统操作(协处理器、调试、模式切换等)。
我们将依次对每一类进行简要介绍。在此之前,让我们先了解一下这些指令类别中共有的功能。
5.1 Instruction set basics
指令集的各个部分都有一些共同的特性。
5.1.1 Constant and immediate values
ARM或Thumb汇编语言指令的长度只有16或32位,这带来了一些问题。这意味着你无法在指令操作码中编码任意的32位值。
在ARM指令集中,由于操作码位用于指定条件码、指令本身以及要使用的寄存器,只有12位可用于指定一个立即数(immediate value)。因此,在如何使用这12位时需要一些创造性的方法。与其让一个大小为-2048到+2047的常数直接指定,这12位被分成一个8位常量和一个4位旋转值。这个旋转值使8位常量值可以通过右移指定次数来旋转,旋转步长从0到30,以2为步长(即0、2、4、6、8…)。
因此,你可以拥有像0x23或0xFF这样的立即数。你还可以生成其他有用的立即数,例如外设或内存块的地址。例如,0x23000000可以通过表示为0x23 ROR 8(请参见第A-35页的ROR)来生成。但是,许多其他常量(如0x3FF)无法通过单个指令生成。对于这些值,你必须将它们分解成多个指令,或者从内存中加载它们。通常情况下,程序员无需关心这些,除非汇编器发出错误并抱怨一个无效的常量。相反,你可以使用汇编语言伪指令来生成所需的常量。
在Thumb中,指令中编码的常量值可以是以下几种之一:
通过将8位值按任意偶数位数旋转到32位字内产生的常量
形式为0x00XY00XY的常量
形式为0xXY00XY00的常量
形式为0xXYXYXYXY的常量
其中XY是范围在0x00到0xFF的十六进制数。
MOVW指令(移动宽值)会将一个16位常量移入寄存器,同时将目标寄存器的高16位清零。MOVT指令(移动顶值)会将一个16位常量移入指定寄存器的高16位,而不改变低16位。这使得可以使用伪指令MOV32来构建任意32位常量。汇编器提供了一些辅助工具,前缀:upper16:
和:lower16:
使你能够从32位常量中提取对应的高或低16位:
MOVW R0, #:lower16:label
MOVT R0, #:upper16:label
虽然这需要两条指令,但它不需要额外空间来存储常量,也不需要从内存中读取数据项。
你还可以使用伪指令LDR Rn, =<constant>
或LDR Rn, =label
。这是旧处理器中没有MOVW和MOVT的唯一选项。汇编器将使用最佳序列将常量生成到指定寄存器(MOV、MVN或从字面池(literal pool)加载)。字面池是代码段中保存常量数据的区域,通常位于函数的结尾或起始处。如果需要手动控制字面池位置,可以使用汇编指令LTORG
(用于armasm)或.ltorg
(用于GNU工具)。加载的寄存器可能是程序计数器,这会导致一个跳转。
这对于绝对寻址或引用当前段以外的内容非常有用;显然,这将导致位置相关的代码。常量的值可以由汇编器或链接器来确定。
ARM工具还提供了相关的伪指令ADR Rn, =label
。它使用与程序计数器(PC)相对的ADD或SUB指令,将标签的地址放入指定的寄存器中,只需一条指令。如果地址距离太远,无法通过这种方式生成,则会使用ADRL
伪指令。这需要两条指令,但提供了更大的范围。这可以用于生成位置无关代码的地址,但只能在同一个代码段内使用。
5.1.2 Conditional execution
ARM指令集的一个特点是几乎所有指令都可以是条件执行的。在大多数其他架构中,只有分支或跳转指令可以执行条件操作。这对于避免在小的if/then/else结构或复合比较中使用条件分支非常有用。
例如,考虑如下代码,用于在寄存器R0和R1中查找较小的值并将结果放入R2中,如以下示例所示。后缀LT表示指令仅在最近的设置标志指令返回小于的情况下执行;GE表示大于或等于。
现在来看一下使用条件MOV指令而不是分支编写的相同代码,见如下示例。
后面的这段代码不仅更短,而且在较早的ARM处理器上执行速度更快。然而,在像Cortex-A9这样的处理器上,这段代码实际上可能更慢,因为指令之间的依赖关系可能导致比分支更长的停顿,而分支预测可以减少甚至消除分支的成本。
需要提醒的是,这种编程风格依赖于某些指令可以选择性地设置状态标志。如果上面的示例中的MOVGE指令自动设置了标志,程序可能无法正确运行。加载和存储指令永远不会设置标志。而对于数据处理操作,您有一个选择。默认情况下,标志会在这些指令执行期间保持不变。如果指令以S作为后缀(例如,MOVS而不是MOV),则指令将设置标志。对于明确的比较指令,S后缀既不是必需的,也不允许。状态标志还可以通过使用专用的PSR(程序状态寄存器)手动设置。
操作指令(MSR)。一些指令根据来自ALU的进位设置进位标志(C),而其他指令则根据桶式移位器的进位设置(桶式移位器在一个时钟周期内按指定的位数移动数据字)。
Thumb-2技术还引入了一条If-Then(IT)指令,为多达四个连续指令提供条件执行。这些条件可以完全相同,或者其中一些是其他的逆条件。IT块中的指令也必须指定要应用的条件码。
IT是一条16位指令,它使用条件码后缀,允许几乎所有Thumb指令根据ALU标志的值进行条件执行。该指令的语法为IT{x{y{z}}},其中x、y和z指定IT块中可选指令的条件切换,分别表示Then(T)或Else(E),例如,ITTET。
例如:
ITT EQ
SUBEQ r1, r1, #1
ADDEQ r0, r0, #60
通常,IT指令是由汇编器自动生成的,而不是手工编写的。通常会改变条件码标志的16位指令,在IT块内不会这样做,除了CMP、CMN和TST,它们的唯一作用就是设置标志。在IT块中使用的指令有一些限制。IT块内可能发生异常,当前的if-then状态存储在CPSR中,因此在异常进入时复制到SPSR中,这样当异常返回时,IT块的执行可以正确恢复。
某些指令总是设置标志且没有其他效果。它们是CMP、CMN、TST和TEQ,与SUBS、ADDS、ANDS和EORS类似,但ALU计算的结果仅用于更新标志,而不会放入寄存器中。
下表列出了可以附加到大多数指令的15个条件码。
5.1.3 Status flags and condition codes
ARM处理器有一个当前程序状态寄存器(CPSR),其中包含四个状态标志:零标志 (Z)、负标志 (N)、进位标志 © 和溢出标志 (V)。下表显示了这些标志位在设置标志操作时的值。
如果无符号操作的结果溢出了32位结果寄存器,则会设置进位标志 © 位。例如,这个位可以用于使用32位操作来实现64位(或更长)算术运算。
溢出标志 (V) 位的工作方式与C位相同,但适用于有符号操作。0x7FFFFFFF是32位中可表示的最大有符号正整数。例如,如果将2加到这个值,结果将是0x80000001,一个很大的负数。V位被设置以指示从位[30]到位[31]的溢出或下溢。
5.2 Data processing operations
这些基本上是核心的基本算术和逻辑操作。它们通常具有略微不同的格式和规则,并在核心的专用单元中执行。
ARM内核只能对寄存器进行数据处理,不能直接对内存进行操作。数据处理指令(大多数情况下)使用一个目标寄存器和两个源操作数。基本格式可以视为操作码,后面可选跟随一个条件码,再可选跟随一个S(设置标志),格式如下:
Operation{cond}{S} Rd, Rn, Operand2
下表总结了数据处理汇编语言指令,列出了它们的助记符操作码、操作数以及对其功能的简要描述。附录A对所有可用指令提供了更详细的描述。
对于大多数程序员来说,这些指令的用途和功能将是显而易见的,但有些指令需要额外的解释。
在算术操作中,请注意移动操作指令MOV和MVN只需要一个操作数(这一操作数被视为操作数2,以实现最大的灵活性,稍后我们将看到)。RSB进行反向减法操作——也就是说,它将第一个操作数从第二个操作数中减去。这条指令是必要的,因为第一个操作数的灵活性较差——它只能是一个寄存器值。因此,要编写R0 = 100 – R1
,你必须使用RSB R0, R1, #100
,因为SUB R0, #100, R1
是不合法的指令。
指令ADC和SBC执行带进位的加法和减法操作。这使你能够对超过32位的值进行算术运算。
逻辑操作基本上与对应的C语言运算符相同。请注意使用ORR而不是OR,这是因为原始ARM指令集对所有数据处理操作都使用了三个字母的缩写。BIC指令对一个寄存器与操作数2的反转值执行AND操作。例如,如果你想清除寄存器R0的第[11]位,可以使用指令BIC R0, R0, #0x800
。
第二个操作数0x800只有第[11]位被设置为1,其他所有位都为0。BIC指令将这个操作数取反,将除了第[11]位之外的所有位设置为逻辑1。然后将这个值与R0中的值进行AND操作,效果是清除第[11]位,结果会被写回R0。
比较和测试指令会修改CPSR(没有其他影响)。
5.2.1 Operand 2 and the barrel shifter
所有数据处理操作的第一个操作数必须始终是一个寄存器。第二个操作数则灵活得多,可以是一个立即数(#x)、一个寄存器(Rm),或一个经过立即数或寄存器移位的寄存器“Rm, shift #x”或“Rm, shift Rs”。共有五种移位操作:左移(LSL)、逻辑右移(LSR)、算术右移(ASR)、右旋转(ROR)和扩展右旋转(RRX)。
右移操作会在寄存器的高位创建空位。这种情况下,必须区分逻辑移位(将0插入最高位)和算术移位(用寄存器的第[31]位符号位填充空位)。因此,ASR操作通常用于有符号值,LSR则用于无符号值。对于左移操作则不需要这样的区分,左移总是将0插入到最低有效位。
因此,与许多汇编语言不同,ARM汇编语言不需要显式的移位指令。相反,可以使用MOV指令来实现移位和旋转。例如,R0 = R1 >> 2
可以通过 MOV R0, R1, LSR #2
来实现。同样,通常会将移位与ADD、SUB或其他指令结合使用。例如,要将R0乘以5,可以这样写:
ADD R0, R0, R0, LSL #2
左移n位实际上相当于乘以2的n次方,因此上面的操作有效地将R0 = R0 + (4 × R0)
。右移提供相应的除法操作,尽管ASR在对负值进行舍入时与C语言中的除法不同。
除了乘法和除法之外,移位操作数的另一个常见用途是数组索引查找。考虑这样一种情况:R1指向一个32位整型(int)数组的基址,R2是指向该数组第n个元素的索引。可以使用单个加载指令,通过计算R1 + (R2 × 4)
来获取适当的地址。以下示例提供了ARM指令中使用的不同操作数2类型的示例。
5.2.2 Multiplication operations
乘法操作非常容易理解。需要注意的一个关键限制是,乘法操作无法直接使用立即数。乘法只能对寄存器中的值进行操作。要用一个常数进行乘法运算,可能需要先将该常数加载到一个寄存器中。
较新版本的ARM处理器增加了更多的乘法指令,为8位、16位和32位数据提供了多种可能性。我们将在讨论DSP指令时,在整数SIMD指令中考虑这些内容。
下表总结了乘法汇编语言指令,列出了它们的助记符操作码、操作数以及对其功能的简要描述。
5.2.3 Additional multiplies
乘法操作提供了将一个32位寄存器与另一个32位寄存器相乘的方法,以生成32位结果或64位有符号或无符号结果。在所有情况下,都可以选择将一个32位或64位值累加到结果中。ARM还增加了额外的乘法指令,包括有符号最高有效字乘法指令:SMMUL、SMMLA和SMMLS。这些指令执行32×32位的乘法,其结果是乘积的高32位,低32位被舍弃。结果可以通过添加后缀R进行四舍五入,否则将截断。UMMAL(无符号乘法累加长)指令执行32×32位乘法,并将两个32位寄存器的内容相加。
5.2.4 Integer SIMD instructions
单指令多数据(SIMD)指令最早在ARMv6架构中引入,提供了在32位寄存器内对8位和16位的数据进行打包、提取和解包的能力,并能够对这些打包的数据执行多种算术操作,如加、减、比较或乘法等,这些都可以通过一条指令完成。这些指令不应与以下内容混淆。
在ARMv7架构中引入了更强大的高级SIMD(NEON)操作,并在第7章以及《ARM® NEON™ Programmer’s Guide》中进行了详细介绍。
整数寄存器SIMD指令
ARMv6 SIMD操作使用CPSR(当前程序状态寄存器)中的GE(大于或等于)标志。这些标志与普通条件标志不同。每个字中的四个字节位置都有对应的标志。常规数据处理操作会产生一个结果,并设置N、Z、C和V标志。SIMD操作最多可产生四个输出,并仅设置GE标志以指示溢出。MSR和MRS指令可以用于直接写入或读取这些标志。
SIMD指令的一般形式是每个寄存器中的子字节数量会并行操作(例如,可以执行四个字节大小的ADD操作),GE标志根据指令的结果被设置或清除。可以使用适当的前缀来指定不同类型的加法和减法操作。例如,QADD16对寄存器内的半字进行饱和加法。SADD/UADD8和SSUB/USUB8会分别独立设置GE标志,而SADD/UADD16和SSUB/USUB16则会根据上半字结果将GE位[3:2]一起设置,并根据下半字结果将GE位[1:0]一起设置。
还提供了ASX和SAX指令类别,它们可以反转一个操作数的半字并对加/减或减/加的并行对进行操作。与之前描述的ADD和SUB指令一样,这些指令存在无符号(UASX/USAX)、有符号(SASX/SSAX)和饱和(QASX/QSAX)版本。
上图中显示的SADD16指令展示了如何通过一条指令执行两个独立的加法操作。寄存器R3和R0的高半字相加,结果存入寄存器R1的高半字;寄存器R3和R0的低半字相加,结果存入寄存器R1的低半字。CPSR中的GE[3:2]位根据高半字的结果进行设置,GE[1:0]位根据低半字的结果进行设置。在每种情况下,溢出信息都会复制到指定的位对中。
整数寄存器SIMD乘法
与其他SIMD操作一样,这些乘法操作在寄存器内的子字上并行执行。指令还可以包含累加选项,并可以指定加法或减法操作。指令包括:
SMUAD(SIMD乘法和加法且无累加)
SMUSD(SIMD乘法和减法且无累加)
SMLAD(乘法和加法且带累加)
SMLSD(乘法和减法且带累加)
在D之前添加L(long)表示64位累加。
使用X(交换,eXchange)后缀表示在计算之前,Rm中的半字会被交换。
如果累加溢出,则设置Q标志。
下图中显示的SMUSD指令执行了两个有符号的16位乘法(顶部×顶部和底部×底部),然后将两个结果相减。这种操作在处理具有实数和虚数部分的复数运算时非常有用,这是滤波算法中的常见任务。
绝对差值之和
计算绝对差值之和是常见视频编解码器中运动矢量估计组件的关键操作,通常在像素数据数组上执行。它计算寄存器Rn和Rm中一个字内字节的绝对差值之和,并加上存储在Ra中的值,然后将结果存储在Rd中。
数据打包和解包
在许多视频和音频编解码器中,打包数据是很常见的(视频数据通常表示为8位像素数据的打包数组,音频数据可能使用打包的16位样本),网络协议中也经常出现。在ARMv6架构添加额外指令之前,这些数据必须使用LDRH和LDRB指令加载,或者以字为单位加载,然后使用移位和位清除操作进行解包;这两种方法都相对效率较低。打包(PKHBT,PKHTB)指令允许从寄存器的任意位置提取16位或8位值,并将其打包到另一个寄存器中。解包指令(UXTH,UXTB,以及许多变体,包括带符号的和加法操作)可以从寄存器的任意位位置提取8位或16位值。
这使得可以使用字或双字加载高效地将内存中的打包数据序列加载出来,解包到独立的寄存器值中进行操作,然后再将其打包回寄存器,以便高效地写入内存。
在上图所示的示例中,R0包含两个独立的16位值,分别用A和B表示。你可以使用UXTH指令将这两个半字解包到寄存器中以便后续处理,然后使用PKHBT指令将两个寄存器中的半字数据打包到一个寄存器中。
在每种情况下,都可以用MOV指令结合LSL或LSR指令来替换解包指令,但在这种情况下,使用了一条用于操作寄存器部分的指令。
字节选择
SEL指令允许你根据CPSR中的GE[3:0]位的值,从第一个或第二个操作数的对应字节中选择每个结果字节。打包数据的算术操作会在加法或减法操作后设置这些位,随后可以使用SEL指令来提取数据的部分——例如,找出每个位置上两个字节中较小的那个。
5.3 Memory instructions
ARM内核仅对寄存器执行算术逻辑单元(ALU)操作。唯一支持的内存操作是加载(将数据从内存读取到寄存器)或存储(将数据从寄存器写入内存)。LDR和STR指令可以像其他指令一样有条件地执行。
您可以通过在指令后附加B(字节)、H(半字)或D(双字,64位)来指定加载或存储传输的大小,例如LDRB。对于加载操作,还可以使用额外的S来表示有符号字节或半字(SB表示有符号字节,SH表示有符号半字)。有关示例,请参见相关内容。这种方法很有用,因为如果将8位或16位的数据加载到32位寄存器中,必须决定如何处理寄存器的最高有效位。无符号数字会被零扩展(即寄存器的最高16或24位被设置为零),但对于有符号数字,则需要将符号位(字节的第[7]位或半字的第[15]位)复制到寄存器的高16位(或24位)。
5.3.1 Addressing modes
加载和存储操作可以使用多种寻址模式。括号中的数字对应于如下示例:
寄存器寻址 - 地址存储在一个寄存器中 (1)。
预索引寻址 - 在内存访问之前将偏移量添加到基址寄存器。其基本形式为
LDR Rd, [Rn, Op2]
。偏移量可以是正数或负数,并且可以是一个立即数或另一个带有可选移位的寄存器 (2)、(3)。带写回的预索引 - 通过在指令后添加感叹号(!)来表示。在内存访问之后,基址寄存器将通过添加偏移量值进行更新 (4)。
带写回的后索引 - 在这种情况下,偏移量值写在方括号之后。基址寄存器的地址用于内存访问,偏移量在内存访问之后添加到基址寄存器 (5)。
5.3.2 Multiple transfers
加载和存储多重指令(Load and Store Multiple)允许连续的字从内存中读取或写入内存。这些指令对于栈操作和内存复制非常有用。只能对字值以这种方式操作,并且必须使用字对齐的地址。
操作数包括一个基址寄存器(可选的感叹号“!”表示写回基址寄存器),以及用大括号括起来的寄存器列表。寄存器列表用逗号分隔,范围用连字符表示。寄存器的加载或存储顺序与列表中指定的顺序无关。相反,操作以固定的方式进行,编号最小的寄存器始终映射到最低地址。
例如:
LDMIA R10!, { R0-R3, R12 }
这条指令从寄存器R10所指向的地址读取五个寄存器的值,并且由于指定了写回操作,在结束时将R10增加20(5 × 4字节)。
指令还必须指定如何从基址寄存器Rd开始操作。四种可能的方式是:IA/IB(后增/先增)和DA/DB(后减/先减)。这些也可以用别名(FD、FA、ED和EA)来表示,这些别名从堆栈的角度工作,指定堆栈指针是指向堆栈顶部的已满位置还是空位置,以及堆栈在内存中是向上还是向下增长。
按照惯例,在基于ARM处理器的系统中,堆栈仅使用“满递减(FD)”选项。这意味着堆栈指针指向堆栈内存中最后填充的位置,并且在每次向堆栈压入新的数据项时,堆栈指针会递减。
例如:
STMFD sp!, {r0-r5} ; 压入到满递减堆栈
LDMFD sp!, {r0-r5} ; 从满递减堆栈弹出
下图显示了将两个寄存器压入堆栈的过程。在执行STMFD(PUSH)指令之前,堆栈指针指向堆栈中最后占用的字。在指令执行完成后,堆栈指针递减了8(两个字),两个寄存器的内容已写入内存,编号最小的寄存器被写入最低的内存地址。
5.4 Branches
指令集提供了多种不同类型的分支指令。对于简单的相对分支(即相对于当前地址的偏移),使用B指令。对于需要将返回地址存储在链接寄存器中的子程序调用,使用BL指令。
如果需要切换指令集(从ARM切换到Thumb或从Thumb切换到ARM),使用BX或BLX指令。你还可以指定将PC作为正常数据处理操作(例如ADD或SUB)的结果目标寄存器,但这通常不被推荐,且在Thumb模式中不支持。另一种分支指令类型可以通过加载(LDR)指令将PC作为目标,或通过加载多重(LDM)指令,或堆栈弹出(POP)指令将PC放在要加载的寄存器列表中实现。
Thumb指令集中有比较并分支指令,将CMP指令和条件分支融合在一起,但不会改变CPSR的条件码标志。该指令有两个操作码:CBZ(如果Rn为零则比较并跳转到标签)和CBNZ(如果Rn不为零则比较并跳转到标签)。这些指令只能在4到130字节范围内向前分支。Thumb还有TBB(表分支字节)和TBH(表分支半字)指令。这些指令从偏移表(字节或半字大小)中读取一个值,并执行向前PC相对的分支,分支的偏移量是从表中返回的字节或半字值的两倍。这些指令需要在一个寄存器中指定表的基地址,在另一个寄存器中指定索引。
5.5 Saturating arithmetic
饱和算术通常用于音频和视频编解码器中。当计算结果高于(或低于)可以表示的最大正数(或负数)时,不会发生溢出。相反,结果将被设置为最大正值或负值(即饱和)。ARM指令集中包含了许多支持此类算法的指令。
5.5.1 Saturated arithmetic instructions
ARM饱和算术指令可以对字节、字或半字大小的值进行操作。例如,QADD8和QSUB8指令中的8表示它们对字节大小的值进行操作。操作的结果将饱和到可能的最大正数或负数。如果结果会溢出并被饱和,则会设置溢出标志(CPSR的Q位)。该标志被称为粘性标志,一旦被设置,它将保持设置状态,直到通过对CPSR的写操作显式清除它。
指令集提供了具有这种行为的特殊指令QSUB和QADD。此外,QDSUB和QDADD指令用于支持Q15或Q31定点算术。这些指令在执行指定的加法或减法之前,将第二个操作数加倍并饱和。
计数前导零(CLZ)指令返回设置的最高有效位之前的0位数量。这对于归一化和某些除法算法非常有用。要将一个值饱和到特定的位位置(实际上是饱和到某个2的幂),可以使用USAT或SSAT(无符号或有符号)饱和操作。USAT16和SSAT16允许对寄存器中包含的两个半字值进行饱和操作。
5.6 Miscellaneous instructions
剩余的指令涵盖协处理器、监督调用、PSR修改、字节反转、缓存预取、位操作等操作。
5.6.1 Coprocessor instructions
协处理器指令占用了ARM指令集的一部分。最多可以实现16个协处理器,编号为0到15(CP0、CP1……CP15)。这些协处理器可以是内部的(内置于处理器中)或通过专用接口外部连接的。在较旧的处理器中,外部协处理器的使用并不常见,并且在Cortex-A系列中完全不支持。
协处理器15是一个内置协处理器,它提供了对许多核心功能的控制,包括缓存和MMU。
协处理器14是一个内置协处理器,它控制核心的硬件调试设施,例如断点单元。
协处理器10和11提供对系统中浮点和NEON硬件的访问。
如果执行了一条协处理器指令,但系统中不存在相应的协处理器,则会发生未定义指令异常。
协处理器指令有五个类别:
CDP – 启动协处理器数据处理操作。
MRC – 从协处理器寄存器移动到ARM寄存器。
MCR – 从ARM寄存器移动到协处理器寄存器。
LDC – 从内存加载到协处理器寄存器。
STC – 将协处理器寄存器存储到内存。
这些指令还有多寄存器和其他变体:
MRRC – 将协处理器的值传输到一对ARM寄存器。
MCCR – 将一对ARM寄存器传输到协处理器。
LDCL – 从多个寄存器读取协处理器寄存器。
STCL – 将协处理器寄存器写入多个寄存器。
5.6.2 SVC
SVC(监督调用)指令在执行时会引发监督调用异常。该指令包含一个24位(ARM)或8位(Thumb)的数值,可以由SVC处理程序代码进行检查。通过SVC机制,操作系统可以指定一组在用户模式下运行的应用程序可以请求的特权操作。该指令最初被称为SWI(软件中断)。
5.6.3 PSR modification
有几条指令可以实现对PSR的读写操作:
MRS将CPSR或SPSR的值传送到一个通用寄存器中。MSR则将通用寄存器的值传送到CPSR或SPSR中。可以更新整个状态寄存器或其中的一部分。在用户模式下,所有位都可以读取,但只能修改条件标志(_f)位。
在特权模式下,可以使用更改处理器状态(CPS)指令直接修改CPSR中的模式和中断启用或禁用(I和F)位。
SETEND指令修改CPSR的一个位E(字节序)位。它可以在混合字节序数据的系统中使用,以临时在小端和大端数据访问之间切换。
5.6.4 Bit manipulation
有一些指令可以在寄存器中实现值的位操作:
BFI(位域插入) 指令允许将一个寄存器底部的相邻位(通过提供宽度值和最低有效位位置指定)插入到目标寄存器中的指定位置。
BFC(位域清除) 指令允许将寄存器中的相邻位清除。
SBFX和UBFX 指令(有符号和无符号位域提取)将一个寄存器中的相邻位复制到另一个寄存器的最低有效位,并根据需要进行符号扩展或零扩展,直至32位。
RBIT 指令将寄存器中所有位的顺序颠倒。
5.6.5 Cache preload
提供了两个指令:PLD(数据缓存预取)和PLI(指令缓存预取)。这两条指令作为提示,指示内存系统即将访问指定地址。如果未支持该预取操作的实现会将预取视为NOP。作为PLD指令参数指定的任何非法地址都不会导致数据中止异常。
5.6.6 Byte reversal
字节反转指令对于处理与对端字节序或其他数据重新排序操作时非常有用:
REV 指令反转一个字中的字节顺序。
REV16 指令反转寄存器中每个半字中的字节顺序。
REVSH 指令反转底部两个字节,并对其进行符号扩展到32位。
下图展示了REV指令的操作,显示了寄存器内四个字节在反转后的排列顺序。
5.6.7 Other instructions
还有一些其他指令可用:
断点指令(BKPT) 将导致预取中止,或使内核进入调试状态(取决于处理器是配置为监控模式还是暂停模式调试)。该指令由调试器使用。
等待中断(WFI) 将内核置于待机模式,内核停止执行,直到被中断或调试事件唤醒。如果在禁用中断的情况下执行WFI,中断仍会唤醒内核,但不会触发中断异常。内核将继续执行WFI之后的指令。在较早的ARM处理器中,WFI是作为CP15操作实现的。
无操作(NOP) 什么都不做。执行时间不一定会被保证,因此NOP指令不应用于在代码中插入定时延迟。它的用途是作为填充。
等待事件(WFE) 指令以类似于WFI的方式将内核置于待机模式。内核将进入睡眠状态,直到被另一个执行REV指令的内核生成的事件唤醒。中断或调试事件也会使内核醒来。
发送事件(SEV) 指令用于生成唤醒事件,这些事件可能会唤醒集群中的其他内核。
相关链接cortex-A:
【ARM中文手册】第二章 ARM Architecture and Processors
【ARM中文手册】第三章 ARM Processor Modes and Registers
【ARM中文手册】第四章 Introduction to Assembly Language
......
相关链接cortex-R:
《ARM Cortex-R 学习指南》-【第二章】-ARM 架构与处理器
《ARM Cortex-R 学习指南》-【第三章 】-ARM 处理器模式与寄存器
《ARM Cortex-R 学习指南》-【第四章】-汇编语言简介
《ARM Cortex-R 学习指南》-【第五章】-统一汇编语言指令
......
经典课程: