6. システムコールとその他の命令
これまで演算命令、分岐命令、 ロード/ストア命令、複数レジスタのロード/ストア命令を見てきましたが、 今回はそれ以外の命令の概要です。まず、非常に重要な Linuxのシステムコールを呼び出すために使用する SVC命令(SWI から名前が変更されました)から始めましょう。
ソフトウェア割り込み命令
ソフトウェア割り込み命令は、Linux ではカーネルのシステムコールの呼び出しに使用されます。
SVC が実行されるとスーパーバイザモード(カーネルモード)に移行し、CPSR は SPSR_svc に待避され、
プログラムカウンタ(PC) に メモリアドレス 0x00000008 に保存されたSVC 例外ベクタ
(例外処理ルーチンの先頭アドレス) がロードされます。
「SVC命令を実行するとカーネルのシステムコール処理ルーチンに制御が渡る」と覚えておきましょう。
オペコード | オペランド | 動作 | 意味 |
---|---|---|---|
SVC | <24bit_number> | PC=mem[0x00000008] SPSR_svc = CPSR | ソフトウェア割り込み |
SVC命令の、下位24ビットは無視され、SVC命令の実行に影響しませんが、 Linux(OABI)ではシステムコール番号 ( に 0x900000 を加えたもの ) を指定します。 カーネルの割り込みハンドラ(システムコール処理ルーチン)側でSVC命令自体を読み出して システムコール番号を抽出しています。その後、システムコールのジャンプテーブルから 特定のシステムコールに制御を渡します。 興味のある方はカーネルソース /usr/src/linux/arch/arm/kernel/entry-common.S の ENTRY(vector_swi) から正確に書いてありますので参考にして下さい。 (^-^)
アセンブラから Linux のシステムコールを使う場合は、 システムコールの引数を r0 レジスタから順 (最大 r6 まで、現在のカーネルでは r5 まで) に値を設定して、 SVC のオペランドにシステムコール番号 (に 0x900000 を加えたもの) を指定することで システムコールを呼び出すことができます。システムコールの返り値は r0 に設定されます。
たとえば、プログラムを終了させるシステムコール番号1番のシステムコールを実行するには、
mov r0, #0 swi #0x900001 @ sys_exit
のようにして呼び出します。簡単ですね。
玄箱PRO や Debian のarmel など EABIのLinuxではシステムコール番号1番のシステムコールを実行するには、
mov r0, #0 mov r7, #1 @ sys_exit swi #0
のように r7 レジスタにシステムコール番号を設定して呼び出します。SVC のオペランドは 0 とします。
システムコールの種類、番号、引数の一覧はこちらを参照してください。 個々のシステムコールの使用方法は今後解説する予定です。
乗算命令
積算命令 (MUL) は32ビットの乗算を行います。積和命令 (MLA) は乗算と加算を同時に 行う命令です。32ビット同士の積算で結果も32ビットであるため、 下位32ビットは符号付き(2の補数)、符号無しのどちらの場合も同じになります。
倍精度積算(multiply long)命令は、32ビットの整数積算を行い、64ビットの結果を返します。積算と積和(64ビット整数の加算も行う)、符号付きと符合無し演算があり、計4種類の命令(UMULL,UMULAL,SMULL,SMULAL)があります。
オペコード | オペランド | 動作 | 意味 |
---|---|---|---|
MLA<cd><S> | Rd, Rm, Rs, Rn | Rd = Rm * Rs + Rn | 積和(32=32*32+32) |
MUL<cd><S> | Rd, Rm, Rs | Rd = Rm * Rs | 乗算(32=32*32) |
SMLAL<cd><S> | RLo, RHi, Rm, Rs | (RHi, RLo) = Rm * Rs + (RHi, RLo) | 符号付積和(64=32*32+64) |
SMULL<cd><S> | RLo, RHi, Rm, Rs | (RHi, RLo) = Rm * Rs | 符号付乗算(64=32*32) |
UMLAL<cd><S> | RLo, RHi, Rm, Rs | (RHi, RLo) = Rm * Rs + (RHi, RLo) | 積和(64=32*32+64) |
UMULL<cd><S> | RLo, RHi, Rm, Rs | (RHi, RLo) = Rm * Rs | 乗算(64=32*32) |
積算(UMULL, SMULL)命令では2つの32ビット整数から64bitの積算結果を得ます。 結果の下位32ビットが RdLoに、上位32ビットが RdHi に代入されます。 積和算(UMLAL, SMLAL)命令では2つの32ビット整数の積を計算し、その結果に64ビット整数を加えて 64ビットの結果を得ます。 ここで加算される64ビット整数、および結果の64ビット整数はどちらも 下位32ビットとして RdLoが、上位32ビットとして RdHiが使用されます。
UMULL と UMLALは全てのオペランドを符号無しとみなして演算を行い、結果も符号無しで返ります。 SMULL と SMLAL ではオペランドは2の補数形式の符号付きと見なされ、 結果は2の補数形式の符号付きで返ります。
SMLAL(符号付倍精度積和演算)命令は、固定小数点形式の行列の乗算に使うと便利そうです。
ステータスレジスタの操作
MRSおよびMSR命令はステータスレジスタ(PSR : Program Status Register)と汎用レジスタ間の読み書きを行う命令です。 MRS命令はPSRの内容を汎用レジスタへ読み出し、MSR命令は汎用レジスタの内容をPSRへ書き込みます。 またMRS命令では、定数 (イミディエイト) または汎用レジスタの内容を、制御ビットに影響を 与えることなくPSRの条件フラグ(N,Z,C,V)に転送することができます。 指定した値の上位4ビットがPSRに転送されます。
オペコード | オペランド | 動作 | 意味 |
---|---|---|---|
MRS<cd> | Rd, CPSR | Rd=CPSR | |
MRS<cd> | Rd, SPSR | Rd=SPSR | |
MSR<cd> | CPSR_<fields>, Rm | CPSR = field_mask( Rm ) | |
MSR<cd> | CPSR_<fields>, #8bit | CPSR = field_mask( #8ビット定数 ) | |
MSR<cd> | SPSR_<fields>, Rm | SPSR = field_mask( Rm ) | |
MSR<cd> | SPSR_<fields>, #8bit | SPSR = field_mask( #8ビット定数 ) |
8ビット定数の部分は8ビットの値をローテイトして生成できる値を指定することができます。 例えば、#0xF000000 や #0x0770000 が可能です。ローテイトするビット数はアセンブラが 自動的に決定してくれます。
<fields> は次のうちの1つ以上を指定できますが、f 以外は使う必要がないと思います。
c | 制御フィールドマスク (PSR[7:0]) |
x | 拡張フィールドマスク (PSR[15:8]) |
s | 状態フィールドマスク (PSR[23:16) |
f | フラグフィールドマスク (PSR[31:24]) |
PSR には CPSR (Current Program Status Register) と SPSR (Saved Program Status Register) がありますが、 通常のプログラム (ユーザモード) では CPSR だけを使います。
スワップ命令
SWP命令はレジスタとメモリ間で32ビットまたは8ビットのデータを交換します。 レジスタとメモリ間でデータを交換するためには、メモリからレジスタへのの読み出しと レジスタからメモリへの書き込みを行う必要がありますが、割り込みされない状態で これを1命令で実行します。
オペコード | オペランド | 動作 | 意味 |
---|---|---|---|
SWP<cd> | Rd, Rm, [Rn] | Rd=[Rn]; [Rn]=Rm | 交換(32bit) |
SWP<cd>B | Rd, Rm, [Rn] | Rd=[Rn]; [Rn]=Rm | 交換(8bit) |
最初にアドレス Rn の内容を読み出し、次にレジスタ Rm の内容をそのアドレスに書き出します。 そして前に読み出しておいたメモリの内容を Rd に格納します。
この命令は排他制御のためのセマフォの実装に使うようです。
セマフォの取得は:
adr r1, semaphore @ セマフォのメモリアドレス mov r0, #0 swp r0, r0, [r1] @ セマフォを取得(全部) cmp r0, #0 subne r0, r0, #1 swpne r0, r0, [r1] @ 残りを返す(セマフォの値 - 1)
セマフォを返す場合は:
adr r1, semaphore @ セマフォのメモリアドレス mov r0, #0 swp r0, r0, [r1] @ セマフォを取得(全部) add r0, r0, #1 swp r0, r0, [r1] @ セマフォを返す(セマフォの値 + 1)
という感じでしょうか? セマフォの最大値が 1 の場合なら:
adr r1, semaphore @ セマフォのメモリアドレス mov r0, #0 swp r0, r0, [r1] @ セマフォを取得 cmp r0, #0
セマフォを返す場合は:
adr r1, semaphore @ セマフォのメモリアドレス mov r0, #1 swp r0, r0, [r1] @ セマフォを返す
という感じになると思います。この辺は自信ありません。
その他の命令
CLZ命令はRmレジスタの値に関して、最上位にある0ビットの数をRdレジスタに返します。 除算ルーチンで使える命令です。
オペコード | オペランド | 動作 | 意味 |
---|---|---|---|
CLZ{cd} | Rd, Rm | Rd = NoOfZero( Rm ) | Count Leading Zeros |
XScale では、そのほかにもブレークポイント命令(BKPT)、THUMBモード命令、コプロセッサ操作命令 (CDP, LDC, STC, MCR, MRC)、内部アキュムレータ操作命令(MAR, MRA, MIA)、飽和演算の機能を持つ Enhanced DSP 操作命令などを使用できますが、もし使用することがあれば、その都度解説することにします。