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, RmRd = NoOfZero( Rm ) Count Leading Zeros

XScale では、そのほかにもブレークポイント命令(BKPT)、THUMBモード命令、コプロセッサ操作命令 (CDP, LDC, STC, MCR, MRC)、内部アキュムレータ操作命令(MAR, MRA, MIA)、飽和演算の機能を持つ Enhanced DSP 操作命令などを使用できますが、もし使用することがあれば、その都度解説することにします。