主記憶アクセスのアライメント
ARMでは,kバイト読み書きする主記憶アクセス命令には,kの倍数番地しか指定できない.例えば,1ワード(4バイト)読み出すまたは書き込む際は,4の倍数番地を必ず指定しなければならない.第III部では,そうしないと CPUが暴走 する(実行時エラーとなり,例外処理ルーチンに制御を移そうとするが,第III部では例外処理の設定をしていないので,制御不能状態に陥る).
kの倍数番地にアクセスすることを「kバイトに アラインメント されたアクセス」と言う. アラインメント「されていない」アクセスがどのようなものか,プログラム例で見てみよう. light.s を以下のように変更してみる.
- .data セクションに2ワード連続した領域を作り,先頭にラベル x を付ける.
- プログラムの先頭に,x + 1 番地から1ワード読み出す命令列を追加する.
@ 正常に動作しないプログラム
-- 省略 --
_start:
@ 次の3行を挿入
ldr r0, =x
add r0, r0, #1 @ r0 = x + 1
ldr r0, [r0] @ x + 1番地から1ワード読み出す
-- 省略 --
@ 末尾に次の4行を追加
.section .data
x:
.word 0
.word 0
x の値が 0x18030 番地であったとすると,追加した ldr r0, [r0]
では 0x18031 番地から1ワード,つまり 0x18031〜0x18034 番地の内容が読み出される.読み出した値はその後使わないので,プログラムの動作は追加前と変わらないはずである.
しかし,第III部の手順でこれをビルドして実行すると,LEDは点灯しない.ldr r0, [r0]
を実行しようとしたときに アラインメントエラー が発生し,この節の冒頭に書いたように暴走するからである.
つまり,ldr r0, [r0]
は4バイト読み出す命令なので,読み出す番地は4の倍数(..., 0x1802c, 0x18030, 0x18034, ...)でなければエラーとなる.
以下のように add r0, r0, #1
を削除すれば,アラインメントエラーはなくなるので,正常に動作するようになる.
@ (余分な行があるが) 正常に動作するプログラム
-- 省略 --
_start:
@ 次の2行を挿入
ldr r0, =x
ldr r0, [r0] @ x番地から1ワード読み出す
-- 省略 --
@ 末尾に次の4行を追加
.section .data
x:
.word 0
.word 0
もう一つ,アラインメントエラーの例を見てみよう(下記に類似したプログラムを記述してしまう受講生は過去にもしばしば居た).
上と同じプログラム例で,add r0, r0, #1
を削除する代わりに,x の前に1バイト別の領域を確保する(下記).
@ 正常に動作しないプログラム
-- 省略 --
_start:
@ 次の2行を挿入
ldr r0, =x
ldr r0, [r0] @ x番地から1ワード読み出す
-- 省略 --
@ 末尾に次の5行を追加
.section .data
y: .byte 0 @ 別の領域
x:
.word 0
.word 0
このプログラムは 正常に動作しない. .dataセクションの先頭に1バイトの領域があることで,x の値が4の倍数でなくなる からだ (.dataセクションの先頭が 0x18030 番地だった場合,y = 0x18030,x = 0x18031 になる).
この例のように,バイト単位の領域を確保する場合は,ワード単位の領域のアラインメントを崩さないか注意する必要がある.この例の場合は,y が指す領域を4バイトに広げるか,y が指す領域を x より後に確保すればよい.
k = 1 の場合(LDRB命令・STRB命令による1バイトの読み書き)は,(番地はすべて1の倍数なので)アラインメントを気にしなくてよい.
上記はARM CPUの仕様なので,本来は第II部でも第III部でも同じだが,実は第II部では,OSがアラインメントエラーに対する回復処理(例外処理ルーチンに制御が移る → 2回に分けて読み出しまたは書き込み → 元の実行位置に戻る)をしてくれるので,アラインメントエラーのあるプログラムでも動作していた.ただし実行は少し遅くなる.
i386の場合,アラインメントされていないアクセスでもエラーにはならないが,アラインメントされている方が実行が速くなる. 多くのCPUにおいて,アラインメントは必須か,必須でなくとも性能に影響する.