arm指令集2(转)

 

算术和逻辑指令

ADC : 带进位的加法

(Addition with Carry)html

ADC{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 + op_2 + carry

ADC 将把两个操做数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就能够作比 32 位大的加法。下列例子将加两个 128 位的数。
128 位结果: 寄存器 0、一、二、和 3
第一个 128 位数: 寄存器 四、五、六、和 7
第二个 128 位数: 寄存器 八、九、十、和 11。ide

ADDS    R0, R4, R8              ; 加低端的字
  ADCS    R1, R5, R9              ; 加下一个字,带进位
  ADCS    R2, R6, R10             ; 加第三个字,带进位
  ADCS    R3, R7, R11             ; 加高端的字,带进位

若是若是要作这样的加法,不要忘记设置 S 后缀来更改进位标志。oop

 

ADD : 加法

(Addition)测试

ADD{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 + op_2

ADD 将把两个操做数加起来,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:ui

ADD     R0, R1, R2              ; R0 = R1 + R2
  ADD     R0, R1, #256            ; R0 = R1 + 256
  ADD     R0, R2, R3,LSL#1        ; R0 = R2 + (R3 << 1)

加法能够在有符号和无符号数上进行。url

 

 

AND : 逻辑与

(logical AND)spa

AND{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 AND op_2

AND 将在两个操做数上进行逻辑与,把结果放置到目的寄存器中;对屏蔽你要在上面工做的位颇有用。 操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:操作系统

AND     R0, R0, #3              ; R0 = 保持 R0 的位 0 和 1,丢弃其他的位。

AND 的真值表(两者都是 1 则结果为 1):3d

Op_1   Op_2   结果

  0      0      0
  0      1      0
  1      0      0
  1      1      1
 

 

BIC : 位清除

(Bit Clear)code

BIC{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 AND (!op_2)

BIC 是在一个字中清除位的一种方法,与 OR 位设置是相反的操做。操做数 2 是一个 32 位位掩码(mask)。若是若是在掩码中设置了某一位,则清除这一位。未设置的掩码位指示此位保持不变。

BIC     R0, R0, #%1011          ; 清除 R0 中的位 0、一、和 3。保持其他的不变。

BIC 真值表 :

Op_1   Op_2   结果

  0      0      0
  0      1      0
  1      0      1
  1      1      0

译注:逻辑表达式为 Op_1 AND NOT Op_2

 

 

EOR : 逻辑异或

(logical Exclusive OR)

EOR{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 EOR op_2

EOR 将在两个操做数上进行逻辑异或,把结果放置到目的寄存器中;对反转特定的位有用。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:

EOR     R0, R0, #3              ; 反转 R0 中的位 0 和 1

EOR 真值表(两者不一样则结果为 1):

Op_1   Op_2   结果

  0      0      0
  0      1      1
  1      0      1
  1      1      0
 

 

MOV : 传送

(Move)

MOV{条件}{S}  <dest>, <op 1>

                dest = op_1

MOV 从另外一个寄存器、被移位的寄存器、或一个当即值装载一个值到目的寄存器。你能够指定相同的寄存器来实现 NOP 指令的效果,你还能够专门移位一个寄存器:

MOV     R0, R0                  ; R0 = R0... NOP 指令

  MOV     R0, R0, LSL#3           ; R0 = R0 * 8

若是 R15 是目的寄存器,将修改程序计数器或标志。这用于返回到调用代码,方法是把链接寄存器的内容传送到 R15:

MOV     PC, R14                 ; 退出到调用者

  MOVS    PC, R14                 ; 退出到调用者并恢复标志位
                                    (不听从 32-bit 体系)
 

 

MVN : 传送取反的值

(MoveNegative)

MVN{条件}{S}  <dest>, <op 1>

                dest = !op_1

MVN 从另外一个寄存器、被移位的寄存器、或一个当即值装载一个值到目的寄存器。不一样之处是在传送以前位被反转了,因此把一个被取反的值传送到一个寄存器中。这是逻辑非操做而不是算术操做,这个取反的值加 1 才是它的取负的值:

MVN     R0, #4                  ; R0 = -5

  MVN     R0, #0                  ; R0 = -1
 

 

ORR : 逻辑或

(logical OR)

ORR{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 OR op_2

OR 将在两个操做数上进行逻辑或,把结果放置到目的寄存器中;对设置特定的位有用。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:

ORR     R0, R0, #3              ; 设置 R0 中位 0 和 1

OR 真值表(两者中存在 1 则结果为 1):

Op_1   Op_2   结果

  0      0      0
  0      1      1
  1      0      1
  1      1      1
 

 

RSB : 反向减法

(Reverse Subtraction)

RSB{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_2 - op_1

SUB 用操做数 two 减去操做数 one,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:

RSB     R0, R1, R2              ; R0 = R2 - R1
  RSB     R0, R1, #256            ; R0 = 256 - R1
  RSB     R0, R2, R3,LSL#1        ; R0 = (R3 << 1) - R2

反向减法能够在有符号或无符号数上进行。

 

 

RSC : 带借位的反向减法

(Reverse Subtraction with Carry)

RSC{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_2 - op_1 - !carry

同于 SBC,但倒换了两个操做数的先后位置。

 

 

SBC : 带借位的减法

(Subtraction with Carry)

SBC{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 - op_2 - !carry

SBC 作两个操做数的减法,把结果放置到目的寄存器中。它使用进位标志来表示借位,这样就能够作大于 32 位的减法。SUBSBC 生成进位标志的方式不一样于常规,若是须要借位则清除进位标志。因此,指令要对进位标志进行一个操做 - 在指令执行期间自动的反转此位。

 

 

SUB : 减法

(Subtraction)

SUB{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 - op_2

SUB 用操做数 one 减去操做数 two,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:

SUB     R0, R1, R2              ; R0 = R1 - R2
  SUB     R0, R1, #256            ; R0 = R1 - 256
  SUB     R0, R2, R3,LSL#1        ; R0 = R2 - (R3 << 1)

减法能够在有符号和无符号数上进行。

移位指令

       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 位老是设置的。在单一数据传送指令中指定移位的数量只能用当即值而不能用寄存器。

下面是给不一样的移位类型的六个助记符:

LSL  逻辑左移
  ASL  算术左移
  LSR  逻辑右移
  ASR  算术右移
  ROR  循环右移
  RRX  带扩展的循环右移

ASLLSL 是等同的,能够自由互换。

你能够用一个当即值(从 0 到 31)指定移位数量,或用包含在 0 和 31 之间的一个值的寄存器指定移位数量。

 

 

逻辑或算术左移

(Logical or Arithmetic Shift Left)

Rx, LSL #n    or
  Rx, ASL #n    or
  Rx, LSL Rn    or
  Rx, ASL Rn

接受 Rx 的内容并按用‘n’或在寄存器 Rn 中指定的数量向高有效位方向移位。最低有效位用零来填充。除了概念上的第 33 位(就是被移出的最小的那位)以外丢弃移出最左端的高位,若是逻辑类指令中 S 位被设置了,则此位将成为从桶式移位器退出时进位标志的值。

考虑下列:

MOV    R1, #12
  MOV    R0, R1, LSL#2

在退出时,R0 是 48。 这些指令造成的总和是 R0 = #12, LSL#2 等同于 BASIC 的 R0 = 12 << 2

 

 

逻辑右移

(Logical Shift Right)

Rx, LSR #n    or
  Rx, LSR Rn

它在概念上与左移相对。把全部位向更低有效位方向移动。若是逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >>> shift

 

 

算术右移

(Arithmetic Shift Right)

Rx, ASR #n    or
  Rx, ASR Rn

相似于 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值来填充高位,用来保护补码表示中的符号。若是逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >> shift

 

 

循环右移

(Rotate Right)

Rx, ROR #n    or
  Rx, ROR Rn

循环右移相似于逻辑右移,可是把从右侧移出去的位放置到左侧,若是逻辑类指令中 S 位被设置了,则同时放置到进位标志中,这就是位的‘循环’。一个移位量为 32 的操做将致使输出与输入彻底一致,由于全部位都被移位了 32 个位置,又回到了开始时的位置!

 

 

带扩展的循环右移

(Rotate Right with extend)

Rx, RRX

这是一个 ROR#0 操做,它向右移动一个位置 - 不一样之处是,它使用处理器的进位标志来提供一个要被移位的 33 位的数量。

 

乘法指令

 

指令格式

这两个指令与普通算术指令在对操做数的限制上有所不一样:

  1. 给出的全部操做数、和目的寄存器必须为简单的寄存器。
  2. 你不能对操做数 2 使用当即值或被移位的寄存器。
  3. 目的寄存器和操做数 1 必须是不一样的寄存器。
  4. 最后,你不能指定 R15 为目的寄存器。

MLA : 带累加的乘法

(Multiplication with Accumulate)

MLA{条件}{S}  <dest>, <op 1>, <op 2>, <op 3>

                dest = (op_1 * op_2) + op_3

MLA 的行为同于 MUL,但它把操做数 3 的值加到结果上。这在求总和时有用。

 

 

MUL : 乘法

(Multiplication)

MUL{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 * op_2

MUL 提供 32 位整数乘法。若是操做数是有符号的,能够假定结果也是有符号的。

 

比较指令

 

指令格式

译注:CMP 和 CMP 是算术指令,TEQ 和 TST 是逻辑指令。把它们纳入一类的缘由是它们的 S 位老是设置的,就是说,它们老是影响标志位。

 

CMN : 比较取负的值

(Compare Negative)

CMN{条件}{P}  <op 1>, <op 2>

                status = op_1 - (- op_2)

CMN 同于 CMP,但它容许你与小负值(操做数 2 的取负的值)进行比较,好比难于用其余方法实现的用于结束列表的 -1。这样与 -1 比较将使用:

CMN     R0, #1                  ; 把 R0 与 -1 进行比较

详情参照 CMP 指令。

 

 

CMP : 比较

(Compare)

CMP{条件}{P}  <op 1>, <op 2>

                status = op_1 - op_2

CMP 容许把一个寄存器的内容如另外一个寄存器的内容或当即值进行比较,更改状态标志来容许进行条件执行。它进行一次减法,但不存储结果,而是正确的更改标志。标志表示的是操做数 1 比操做数 2 如何(大小等)。若是操做数 1 大于操做操做数 2,则此后的有 GT 后缀的指令将能够执行。
明显的,你不须要显式的指定 S 后缀来更改状态标志... 若是你指定了它则被忽略。

 

 

TEQ : 测试等价

(Test Equivalence)

TEQ{条件}{P}  <op 1>, <op 2>

                Status = op_1 EOR op_2

TEQ 相似于 TST。区别是这里的概念上的计算是 EOR 而不是 AND。这提供了一种查看两个操做数是否相同而又不影响进位标志(不象 CMP那样)的方法。加上 P 后缀的 TEQ 还可用于改变 R15 中的标志(在 26-bit 模式中)。详情请参照 psr.html,在 32-bit 模式下如何作请参见这里

 

 

TST : 测试位

(Test bits)

TST{条件}{P}  <op 1>, <op 2>

                Status = op_1 AND op_2

TST 相似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操做数上进行操做并把结果反映到状态标志上。使用 TST 来检查是否设置了特定的位。操做数 1 是要测试的数据字而操做数 2 是一个位掩码。通过测试后,若是匹配则设置 Zero 标志,不然清除它。象 CMP 那样,你不须要指定 S 后缀。

TST     R0, #%1                 ; 测试在 R0 中是否设置了位 0。
 

分支指令

B : 分支

(Branch)
B{条件}  <地址>
 是最简单的分支。一旦遇到一个  指令,ARM 处理器将当即跳转到给定的地址,从那里继续执行。BB
注意存储在分支指令中的实际的值是相对当前的 R15 的值的一个偏移量;而不是一个绝对地址。
它的值由汇编器来计算,它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(+/- 32 M)。
在其余处理器上,你可能常常见到这样的指令:
OPT 1
  LDA &70
  CMP #0
  BEQ Zero
  STA &72
 .Zero RTS
(取自 Acorn Electron User Guide issue 1 page 213)
在 ARM 处理器上,它们将变成下面这些东西:
OPT     1
  ADR     R1, #&70
  LDR     R0, [R1]
  CMP     #0
  BEQ     Zero
  STR     R0, [R1, #2]
 .Zero
  MOV     PC, R14
这不是一个很好的例子,但你能够构想如何更好的去条件执行而不是分支。另外一方面,若是你有大段的代码或者你的代码使用状态标志,那么你可使用条件执行来实现各种分支: 这样一个单一的简单条件执行指令能够替代在其余处理器中存在的全部这些分支和跳转指令。
OPT     1
  ADR     R1, #&70
  LDR     R0, [R1]
  CMP     R0, #0
  STRNE   R0, [R1, #2]
  MOV     PC, R14

BL : 带链接的分支

(Branch with Link)
BL{条件}  <地址>
 是另外一个分支指令。就在分支以前,在寄存器 14 中装载上 R15 的内容。你能够从新装载 R14 到 R15 中来返回到在这个分支以后的那个指令,BL
它是子例程的一个基本但强力的实现。它的做用在屏幕装载器 2 (例子 4)中得以很好的展示...
.load_new_format
      BL     switch_screen_mode
      BL     get_screen_info
      BL     load_palette

    .new_loop
      MOV    R1, R5
      BL     read_byte
      CMP    R0, #255
      BLEQ   read_loop
      STRB   R0, [R2, #1]!
...在这里咱们见到在装载器循环以前调用了三个子例程。接着,一旦知足了条件执行就在循环中调用了 read_byte 子例程。

条件执行

 

 

 

ARM 处理器的一个很是特殊的特征是它的条件执行。咱们指的不是基本的若是进位则分支,ARM 使这个逻辑阶段进一步深化为若是进位则 XXX- 这里的 XXX 是任何东西。

为了举例,下面是 Intel 8086 处理器分支指令的一个列表:

JA    Jump if Above
  JAE   Jump if Above or Equal
  JB    Jump if Below
  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 指令的基本处理步骤。
相关文章
相关标签/搜索