ARM指令集詳解(超詳細!帶實例!)

原文地址

算術和邏輯指令

ADC : 帶進位的加法

(Addition with Carry)

ADC{條件}{S}  <dest>, <op 1>, <op 2>
                dest = op_1 + op_2 + carry

ADC 將把兩個操作數加起來,並把結果放置到目的寄存器中。它使用一個進位標誌位,這樣就可以做比 32 位大的加法。下列例子將加兩個 128 位的數。
128 位結果: 寄存器 0、1、2、和 3
第一個 128 位數: 寄存器 4、5、6、和 7
第二個 128 位數: 寄存器 8、9、10、和 11。

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

如果如果要做這樣的加法,不要忘記設置 S 後綴來更改進位標誌。

ADD : 加法

(Addition)

ADD{條件}{S}  <dest>, <op 1>, <op 2>
                dest = op_1 + op_2

ADD 將把兩個操作數加起來,把結果放置到目的寄存器中。操作數 1 是一個寄存器,操作數 2 可以是一個寄存器,被移位的寄存器,或一個立即值:

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

加法可以在有符號和無符號數上進行。

AND : 邏輯與

(logical AND)

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):

Op_1 Op_2 結果
0 0 0
0 1 0
1 0 0
1 1 1

BIC : 位清除

(Bit Clear)

BIC{條件}{S}  <dest>, <op 1>, <op 2>
                dest = op_1 AND (!op_2)

BIC 是在一個字中清除位的一種方法,與 OR 位設置是相反的操作。操作數 2 是一個 32 位位掩碼(mask)。如果如果在掩碼中設置了某一位,則清除這一位。未設置的掩碼位指示此位保持不變。

BIC     R0, R0, #%1011          ; 清除 R0 中的位 0、1、和 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 位的減法。SUB 和 SBC 生成進位標誌的方式不同於常規,如果需要借位則清除進位標誌。所以,指令要對進位標誌進行一個非操作 - 在指令執行期間自動的反轉此位。

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  帶擴展的循環右移

ASL 和 LSL 是等同的,可以自由互換。

你可以用一個立即值(從 0 到 31)指定移位數量,或用包含在 0 和 31 之間的一個值的寄存器指定移位數量。

LSL/ASL : 邏輯或算術左移

(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

LSR : 邏輯右移

(Logical Shift Right)

Rx, LSR #n    or
Rx, LSR Rn

它在概念上與左移相對。把所有位向更低有效位方向移動。如果邏輯類指令中 S 位被設置了,則把最後被移出最右端的那位放置到進位標誌中。它同於 BASIC 的 register = value >>> shift。

ASR : 算術右移

(Arithmetic Shift Right)

Rx, ASR #n    or
Rx, ASR Rn

類似於 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值來填充高位,用來保護補碼錶示中的符號。如果邏輯類指令中 S 位被設置了,則把最後被移出最右端的那位放置到進位標誌中。它同於 BASIC 的 register = value >> shift。

ROR : 循環右移

(Rotate Right)

Rx, ROR #n    or
Rx, ROR Rn

循環右移類似於邏輯右移,但是把從右側移出去的位放置到左側,如果邏輯類指令中 S 位被設置了,則同時放置到進位標誌中,這就是位的‘循環’。一個移位量爲 32 的操作將導致輸出與輸入完全一致,因爲所有位都被移位了 32 個位置,又回到了開始時的位置!

RRX : 帶擴展的循環右移

(Rotate Right with extend)

Rx, RRX

這是一個 ROR#0 操作,它向右移動一個位置 - 不同之處是,它使用處理器的進位標誌來提供一個要被移位的 33 位的數量。
轉者注:將寄存器內容循環右移一位,空位用原來的C來 填充,移出的最低有效位填入C。

乘法指令

指令格式

這兩個指令與普通算術指令在對操作數的限制上有所不同:

  • 給出的所有操作數、和目的寄存器必須爲簡單的寄存器。
  • 你不能對操作數 2 使用立即值或被移位的寄存器。
  • 目的寄存器和操作數 1
  • 最後,你不能指定 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 位整數乘法。如果操作數是有符號的,可以假定結果也是有符號的。

比較指令

指令格式

譯註:CMN 和 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和BL其實就是函數入口點跳轉之類的東西了,但是這個說明真心看不懂。。。

B : 分支

(Branch)

B{條件}  <地址>

B 是最簡單的分支。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的地址,從那裏繼續執行。
注意存儲在分支指令中的實際的值是相對當前的 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{條件}  <地址>

BL 是另一個分支指令。就在分支之前,在寄存器 14 中裝載上 R15 的內容。你可以重新裝載 R14 到 R15 中來返回到在這個分支之後的那個指令,

它是子例程的一個基本但強力的實現。它的作用在屏幕裝載器 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 指令的基本處理步驟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章