JBE Jump if Below or Equal
JC Jump if Carry
JCXZ Jump if CX Zero (CX is a register that can be used for loop counts)
JE Jump if Equal
JG Jump if Greater than
JGE Jump if Greater than or Equal
JL Jump if Less than
JLE Jump if Less Than or Equal
JMP JuMP
JNA Jump if Not Above
JNAE Jump if Not Above or Equal
JNB Jump if Not Below
JNBE Jump if Not Below or Equal
JNC Jump if No Carry
JNE Jump if Not Equal
JNG Jump if Not Greater than
JNGE Jump if Not Greater than or Equal
JNL Jump if Not Less than
JNLE Jump if Not Less than or Equal
JNO Jump if Not Overflow
JNP Jump if Not Parity
JNS Jump if Not Sign
JNZ Jump if Not Zero
JO Jump if Overflow
JP Jump if Parity
JPE Jump if Parity Even
JPO Jump if Parity Odd
JS Jump if Sign
JZ Jump if Zero
80386 添加了:
JECXZ Jump if ECX Zero
作为对比,ARM 处理器只提供了:
B 分支
BL 带连接的分支
但 ARM 提供了条件执行,你可以不受这个表面上不灵活的方式的限制:
BEQ Branch if EQual
BNE Branch if Not Equal
BVS Branch if oVerflow Set
BVC Branch if oVerflow Clear
BHI Branch if HIgher
BLS Branch if Lower or the Same
BPL Branch if PLus
BMI Branch if MInus
BCS Branch if Carry Set
BCC Branch if Carry Clear
BGE Branch if Greater than or Equal
BGT Branch if Greater Than
BLE Branch if Less than or Equal
BLT Branch if Less Than
BLEQ Branch with Link if EQual
….
BLLT Branch with Link if Less Than
还有两个代码,
AL - ALways,缺省条件所以不须指定
NV - NeVer,不是非常有用。你无论如何不要使用这个代码…
当你发现所有 Bxx 指令实际上是同一个指令的时候,紧要关头就到了。
接着你会想,如果你可以在一个分支指令上加上所有这些条件,那么对一个寄存器装载指令能否加上它们? 答案是可以。
下面是可获得的条件代码的列表:
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,它以相反的方式工作。当用于一个指令的时候,导致更改状态标志。这不是自动发生的 - 除非这些指令的目的是设置状态。例如:
ADD R0, R0, R1
ADDS R0, R0, R1
ADDEQS R0, R0, R1
第一个例子是一个基本的加法(把 R1 的值增加到 R0),它不影响状态寄存器。
第二个例子是同一个加法,只不过它导致更改状态寄存器。
最后一个例子是同一个加法,更改状态寄存器。不同在于它是一个有条件的指令。只有前一个操作的结果是 EQ (如果设置了 Z 标志)的时候它才执行。
下面是条件执行的一个工作中的例子。你把寄存器 0 与存储在寄存器 10 中内容相比较。
如果不等于 R10,则调用一个软件中断,增加它并分支回来再次做这些。否则清除 R10 并返回到调用它的那部分代码(它的地址存储在 R14)。
条件执行的一个例子
.loop ; 标记循环开始位置
CMP R0, R10 ; 把 R0 与 R10 相比较
SWINE &40017 ; 不等于: 调用 SWI &40017
ADDNE R0, R0, #1 ; 向 R0 加 1
BNE loop ; 分支到 'loop'
MOV R10, #0 ; 等于 : 设置 R10 为零
LDMFD R13!, {R0-R12,PC} ; 返回到调用者
注解:
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{条件} <24 位编号>
指令格式
这是一个简单的设施,但可能是最常用的。多数操作系统设施是用 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 &02
或
SWI "OS_Write0"
这些指令实际上是相同的,将被汇编成相同的指令。唯一的不同是第二个指令使用一个字符串来表示 SWI 编号 &02。
在使用采用了字符串编号的程序的时候,在执行之前首先查找这个字符串。
在这里我们不想处理字符串,因为它不能给出它要进行什么的一个真实表示。它们通常用于增进一个程序的清晰程度,但不是实际执行的指令。
让我们再次看一下第一个指令:
SWI &02
这是什么意思? 字面的意思是进入 SWI 处理程序并传递值 &02。在 RISC OS 中这意味着执行编号是 &02 的例程。
它是如何这么作的? 它如何传递 SWI 编号和进入 SWI 处理程序?
如果你查看内存的开始 32 字节(位于 0-&1C)并反汇编它们(查开实际的 ARM 指令)你将见到如下:
地址 内容 反汇编
00000000 : 0..? : E5000030 : STR R0,[R0,#-48]
00000004 : .ó?? : E59FF31C : LDR PC,&00000328
00000008 : .ó?? : E59FF31C : LDR PC,&0000032C
0000000C : .ó?? : E59FF31C : LDR PC,&00000330
00000010 : .ó?? : E59FF31C : LDR PC,&00000334
00000014 : .ó?? : E59FF31C : LDR PC,&00000338
00000018 : .ó?? : E59FF31C : LDR PC,&0000033C
0000001C : 2?? : E3A0A632 : MOV R10,#&3200000
让我们仔细看一下。
除了第一个和最后一个指令之外(它们是特殊情况)你见到的都是把一个新值装载到 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 之后):
1.它从 r14 中减去 4 来获得 SWI 指令的地址。
2.把这个指令装载到一个寄存器。
3.清除这个指令的高端 8 位,去掉了 OpCode 而只剩下的 SWI 编号。
4.使用这个值来找到要被执行的代码的例程的地址(使用查找表等)。
5.恢复寄存器 r0-r12。
6.使处理器离开超级用户模式。
7.跳转到这个例程的地址。
容易吧! ;)
下面是一个例子,来自 ARM610 datasheet:
0x08 B Supervisor
EntryTable
DCD ZeroRtn
DCD ReadCRtn
DCD WriteIRtn
..
Zero EQU 0
ReadC EQU 256
WriteI EQU 512
; SWI 包含需要的例程在位 8-23 中和数据(如果有的话)在位 0-7 中。
; 假定 R13_svc 指向了一个合适的栈
STMFD R13, {r0-r2 , R14}
; 保存工作寄存器和返回地址。
LDR R0,[R14,#-4]
; 得到 SWI 指令。
BIC R0,R0, #0xFF000000
; 清除高端的 8 位。
MOV R1, R0, LSR #8
; 得到例程偏移量。
ADR R2, EntryTable
; 得到入口表(EntryTable)的开始地址。
LDR R15,[R2,R1,LSL #2]
; 分支到正确的例程
WriteIRtn
; 写 R0 中的位 0 - 7 中的字符。
.............
LDMFD R13, {r0-r2 , R15}^
; 恢复工作空间,并返回、恢复处理器模式和标志。
这就是 SWI 指令的基本处理步骤。