算术和逻辑指令
ADC : 带进位的加法
(Addition with Carry)
ADC
将把两个操作数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就可以做比 32 位大的加法。下列例子将加两个 128 位的数。
128 位结果: 寄存器 0、1、2、和 3
第一个 128 位数: 寄存器 4、5、6、和 7
第二个 128 位数: 寄存器 8、9、10、和 11。
如果如果要做这样的加法,不要忘记设置 S 后缀来更改进位标志。
ADD : 加法
(Addition)
ADD
将把两个操作数加起来,把结果放置到目的寄存器中。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
加法可以在有符号和无符号数上进行。
AND : 逻辑与
(logical AND)
AND
将在两个操作数上进行逻辑与,把结果放置到目的寄存器中;对屏蔽你要在上面工作的位很有用。 操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
AND 的真值表(二者都是 1 则结果为 1):
BIC : 位清除
(Bit Clear)
BIC
是在一个字中清除位的一种方法,与 OR 位设置是相反的操作。操作数 2 是一个 32 位位掩码(mask)。如果如果在掩码中设置了某一位,则清除这一位。未设置的掩码位指示此位保持不变。
BIC 真值表 :
EOR : 逻辑异或
(logical Exclusive OR)
EOR
将在两个操作数上进行逻辑异或,把结果放置到目的寄存器中;对反转特定的位有用。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
EOR 真值表(二者不同则结果为 1):
MOV : 传送
(Move)
MOV
从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器。你可以指定相同的寄存器来实现 NOP 指令的效果,你还可以专门移位一个寄存器:
如果 R15 是目的寄存器,将修改程序计数器或标志。这用于返回到调用代码,方法是把连接寄存器的内容传送到 R15:
MVN : 传送取反的值
(Move Negative)
MVN
从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器。不同之处是在传送之前位被反转了,所以把一个被取反的值传送到一个寄存器中。这是逻辑非操作而不是算术操作,这个取反的值加 1 才是它的取负的值:
ORR : 逻辑或
(logical OR)
OR
将在两个操作数上进行逻辑或,把结果放置到目的寄存器中;对设置特定的位有用。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
OR 真值表(二者中存在 1 则结果为 1):
RSB : 反向减法
(Reverse Subtraction)
SUB
用操作数 two 减去操作数 one,把结果放置到目的寄存器中。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
反向减法可以在有符号或无符号数上进行。
RSC : 带借位的反向减法
(Reverse Subtraction with Carry)
同于 SBC
,但倒换了两个操作数的前后位置。
SBC : 带借位的减法
(Subtraction with Carry)
SBC
做两个操作数的减法,把结果放置到目的寄存器中。它使用进位标志来表示借位,这样就可以做大于 32 位的减法。SUB
和 SBC
生成进位标志的方式不同于常规,如果需要借位则清除进位标志。所以,指令要对进位标志进行一个非操作 - 在指令执行期间自动的反转此位。
SUB : 减法
(Subtraction)
SUB
用操作数 one 减去操作数 two,把结果放置到目的寄存器中。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:
减法可以在有符号和无符号数上进行。
移位指令
ARM 处理器组建了可以与数据处理指令(ADC、ADD、AND、BIC、CMN、CMP、EOR、MOV、MVN、ORR、RSB、SBC、SUB、TEQ、TST)一起使用的桶式移位器(barrel shifter)。你还可以使用桶式移位器影响在 LDR/STR 操作中的变址值。
译注:移位操作在 ARM 指令集中不作为单独的指令使用,它是指令格式中是一个字段,在汇编语言中表示为指令中的选项。如果数据处理指令的第二个操作数或者单一数据传送指令中的变址是寄存器,则可以对它进行各种移位操作。如果数据处理指令的第二个操作数是立即值,在指令中用 8 位立即值和 4 位循环移位来表示它,所以对大于 255 的立即值,汇编器尝试通过在指令中设置循环移位数量来表示它,如果不能表示则生成一个错误。在逻辑类指令中,逻辑运算指令由指令中 S 位的设置或清除来确定是否影响进位标志,而比较指令的 S 位总是设置的。在单一数据传送指令中指定移位的数量只能用立即值而不能用寄存器。
下面是给不同的移位类型的六个助记符:
ASL
和 LSL
是等同的,可以自由互换。
你可以用一个立即值(从 0 到 31)指定移位数量,或用包含在 0 和 31 之间的一个值的寄存器指定移位数量。
逻辑或算术左移
(Logical or Arithmetic Shift Left)
接受 Rx 的内容并按用‘n’或在寄存器 Rn 中指定的数量向高有效位方向移位。最低有效位用零来填充。除了概念上的第 33 位(就是被移出的最小的那位)之外丢弃移出最左端的高位,如果逻辑类指令中 S 位被设置了,则此位将成为从桶式移位器退出时进位标志的值。
考虑下列:
在退出时,R0 是 48。 这些指令形成的总和是 R0 = #12, LSL#2
等同于 BASIC 的 R0 = 12 << 2
逻辑右移
(Logical Shift Right)
它在概念上与左移相对。把所有位向更低有效位方向移动。如果逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >>> shift
。
算术右移
(Arithmetic Shift Right)
类似于 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值来填充高位,用来保护补码表示中的符号。如果逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >> shift
。
循环右移
(Rotate Right)
循环右移类似于逻辑右移,但是把从右侧移出去的位放置到左侧,如果逻辑类指令中 S 位被设置了,则同时放置到进位标志中,这就是位的‘循环’。一个移位量为 32 的操作将导致输出与输入完全一致,因为所有位都被移位了 32 个位置,又回到了开始时的位置!
带扩展的循环右移
(Rotate Right with extend)
这是一个 ROR#0 操作,它向右移动一个位置 - 不同之处是,它使用处理器的进位标志来提供一个要被移位的 33 位的数量。
乘法指令 |
这两个指令与普通算术指令在对操作数的限制上有所不同:
- 给出的所有操作数、和目的寄存器必须为简单的寄存器。
- 你不能对操作数 2 使用立即值或被移位的寄存器。
- 目的寄存器和操作数 1 必须是不同的寄存器。
- 最后,你不能指定 R15 为目的寄存器。
MLA : 带累加的乘法
(Multiplication with Accumulate)
MLA
的行为同于 MUL
,但它把操作数 3 的值加到结果上。这在求总和时有用。
MUL : 乘法
(Multiplication)
MUL
提供 32 位整数乘法。如果操作数是有符号的,可以假定结果也是有符号的。
比较指令 |
译注:CMP 和 CMP 是算术指令,TEQ 和 TST 是逻辑指令。把它们归入一类的原因是它们的 S 位总是设置的,就是说,它们总是影响标志位。
CMN : 比较取负的值
(Compare Negative)
CMN
同于 CMP
,但它允许你与小负值(操作数 2 的取负的值)进行比较,比如难于用其他方法实现的用于结束列表的 -1。这样与 -1 比较将使用:
详情参照 CMP
指令。
CMP : 比较
(Compare)
CMP
允许把一个寄存器的内容如另一个寄存器的内容或立即值进行比较,更改状态标志来允许进行条件执行。它进行一次减法,但不存储结果,而是正确的更改标志。标志表示的是操作数 1 比操作数 2 如何(大小等)。如果操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。
明显的,你不需要显式的指定 S
后缀来更改状态标志... 如果你指定了它则被忽略。
TEQ : 测试等价
(Test Equivalence)
TEQ
类似于 TST
。区别是这里的概念上的计算是 EOR 而不是 AND。这提供了一种查看两个操作数是否相同而又不影响进位标志(不象 CMP
那样)的方法。加上 P
后缀的 TEQ
还可用于改变 R15 中的标志(在 26-bit 模式中)。详情请参照 psr.html,在 32-bit 模式下如何做请参见这里。
TST : 测试位
(Test bits)
TST
类似于 CMP
,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 TST
来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。象 CMP
那样,你不需要指定 S
后缀。
分支指令
B : 分支
B
是最简单的分支。一旦遇到一个
B
指令,ARM 处理器将立即跳转到给定的地址,从那里继续执行。
BL : 带连接的分支
BL
是另一个分支指令。就在分支之前,在寄存器 14 中装载上 R15 的内容。你可以重新装载 R14 到 R15 中来返回到在这个分支之后的那个指令,
条件执行 |
ARM 处理器的一个非常特殊的特征是它的条件执行。我们指的不是基本的如果进位则分支,ARM 使这个逻辑阶段进一步深化为如果进位则
XXX
- 这里的 XXX 是任何东西。
为了举例,下面是 Intel 8086 处理器分支指令的一个列表:
AL
- ALways,缺省条件所以不须指定NV
- NeVer,不是非常有用。你无论如何不要使用这个代码...
下面是可获得的条件代码的列表:
-
EQ : 等于
- 如果一次比较之后设置了 Z 标志。 NE : 不等于
- 如果一次比较之后清除了 Z 标志。 VS : 溢出设置
- 如果在一次算术操作之后设置了 V 标志,计算的结果不适合放入一个 32bit 目标寄存器中。 VC : 溢出清除
- 如果清除了 V 标志,与 VS 相反。 HI : 高于(无符号)
- 如果一次比较之后设置了 C 标志 并清除了 Z 标志。 LS : 低于或同于(无符号)
- 如果一次比较操作之后清除了 C 标志 或设置了 Z 标志。 PL : 正号
- 如果一次算术操作之后清除了 N。出于定义‘正号’的目的,零是正数的原因是它不是负数... MI : 负号
- 如果一次算术操作之后设置了 N 标志。 CS : 进位设置
- 如果一次算术操作或移位操作之后设置了 C 标志,操作的结果不能表示为 32bit。你可以把 C 标志当作结果的第 33 位。 CC : 进位清除
- 与 CS 相反。 GE : 大于或等于(有符号)
-
如果一次比较之后...
设置了 N 标志 并设置了 V 标志
或者...
清除了 N 标志 并清除了 V 标志。
GT : 大于(有符号)
-
如果一次比较之后...
设置了 N 标志 并设置了 V 标志
或者...
清除了 N 标志 并清除了 V 标志
并且...
清除了 Z 标志。
LE : 小于或等于(有符号)
-
如果一次比较之后...
设置了 N 标志 并清除了 V 标志
或者...
清除了 N 标志 并设置了 V 标志
并且...
设置了 Z 标志。
LT : 小于(有符号)
-
如果一次比较之后...
设置了 N 标志 并清除了 V 标志。
或者...
清除了 N 标志 并设置了 V 标志。
AL : 总是
- 缺省条件,所以不用明显声明。 NV : 从不
-
不是特别有用,它表示应当永远不执行这个指令。是穷人的 NOP。
包含 NV 是为了完整性(与 AL 相对),你不应该在你的代码中使用它。
S,
它以相反的方式工作。当用于一个指令的时候,导致更改状态标志。这不是自动发生的 - 除非这些指令的目的是设置状态。例如:
第二个例子是同一个加法,只不过它导致更改状态寄存器。
最后一个例子是同一个加法,更改状态寄存器。不同在于它是一个有条件的指令。只有前一个操作的结果是 EQ (如果设置了 Z 标志)的时候它才执行。
下面是条件执行的一个工作中的例子。你把寄存器 0 与存储在寄存器 10 中内容相比较。
如果不等于 R10,则调用一个软件中断,增加它并分支回来再次做这些。否则清除 R10 并返回到调用它的那部分代码(它的地址存储在 R14)。
- SWI 编号就象我写的这样。在 RISC OS 下,它是给 Econet_DoImmediate 的编号。不要字面的接受它,这只是一个例子!
- 你可能以前没见过
LDMFD,
它从栈中装载多个寄存器。在这个例子中,我们从一个完全正式的栈中装载 R0 至 R12 和 R14。关于寄存器装载和存储的更多信息请参阅 str.html。 - 我说要装载 R14。那么为什么要把它放入 PC 中? 原因是此时 R14 存储的值包含返回地址。我们也可以采用:
LDMFD R13!, {R0-R12,R14}
MOV PC, R14
但是直接恢复到 PC 中可以省略这个MOV
语句。 - 最后,这些寄存器很有可能被一个 SWI 调用所占用(依赖于在调用期间执行的代码),所以你最好把你的重要的寄存器压入栈中,以后在恢复它们。
SWI 指令 |
SWI : 软件中断
(Software Interrupt)
这是一个简单的设施,但可能是最常用的。多数操作系统设施是用 SWI 提供的。没有 SWI 的 RISC OS 是不可想象的。
Nava Whiteford 解释了 SWI 是如何工作的(最初在 Frobnicate issue 12½)...
SWI 是什么?
SWI 表示 Software Interrupt。在 RISC OS 中使用 SWI 来访问操作系统例程或第三方生产的模块。许多应用使用模块来给其他应用提供低层外部访问。SWI 的例子有:
- 文件器 SWI,它辅助读写磁盘、设置属性等。
- 打印机驱动器 SWI,用来辅助使用打印并行端口。
- FreeNet/Acorn TCP/IP 协议栈 SWI,用 TCP/IP 协议在 Internet 上发送和接收数据。
在以这种方式使用的时候,SWI 允许操作系统拥有一个模块结构,这意味着用来建立完整的操作系统的所需的代码可以被分割成许多小的部分(模块)和一个模块处理程序(handler)。
当 SWI 处理程序得到对特定的例程编号的一个请求的时候,它找到这个例程的位置并执行它,并传递(有关的)任何数据。
它是如何工作的?
首先查看一下如何使用它。一个 SWI 指令(汇编语言)看起来如下:在这里我们不想处理字符串,因为它不能给出它要进行什么的一个真实表示。它们通常用于增进一个程序的清晰程度,但不是实际执行的指令。
让我们再次看一下第一个指令:
它是如何这么作的? 它如何传递 SWI 编号和进入 SWI 处理程序?
如果你查看内存的开始 32 字节(位于 0-&1C)并反汇编它们(查开实际的 ARM 指令)你将见到如下:
除了第一个和最后一个指令之外(它们是特殊情况)你见到的都是把一个新值装载到 PC (程序计数器)的指令,它们告诉计算机到哪里去执行下一个指令。
还展示了这个值是从内存中的一个地址接受来的。(你可以在 !Zap 主菜单上使用“Read Memory”选项去自己查看一下。)
这看起来好象与 SWI 没多少关系,下面做进一步的说明。
一个 SWI 所做的一切就是把模式改变成超级用户并设置 PC 来执行在地址 &08 处的下一个指令!
把处理器转换到超级用户模式会切换掉两个寄存器 r13 和 r14 并用 r13_svc 和 r14_svc 替换它们。
在进入超级用户模式的时候,还把 r14_svc 设置为在这个 SWI 指令之后的地址。
这个实际上就象一个连接到地址 &08 的分支指令(BL &08),但带有用于一些数据(SWI 编号)的空间。
象我说过的那样,地址 &08 包含跳转到另一个地址的一个指令,就是实际的 SWI 程序的地址!
此时你可能会想“稍等一会! 还有 SWI 编号呢?”。实际上处理器忽略这个值本身。SWI 处理程序使用传递来的 r14_svc 的值来获取它。
下面是完成它的步骤(在存储寄存器 r0-r12 之后):
- 它从 r14 中减去 4 来获得 SWI 指令的地址。
- 把这个指令装载到一个寄存器。
- 清除这个指令的高端 8 位,去掉了 OpCode 而只剩下的 SWI 编号。
- 使用这个值来找到要被执行的代码的例程的地址(使用查找表等)。
- 恢复寄存器 r0-r12。
- 使处理器离开超级用户模式。
- 跳转到这个例程的地址。
下面是一个例子,来自 ARM610 datasheet: