このページの目次
ARM64 システムコール ABI
カバー: svc 命令 → 引数レジスタ → x86-64 との違い → アトミック操作 (LDXR/STXR) → ARM64 固有の syscall → PAC/BTI 対象: ARM64 (AArch64), Linux 3.7+
概要
ARM64 (AArch64) のシステムコールは、svc #0 命令を使用して EL1(カーネルモード)に遷移します。x86-64 の syscall とは異なり、svc はレジスタの自動保存/復元を行いません。すべてのコンテキストスイッチはソフトウェアによって処理されます。ARM64 の syscall ABI はより直接的ですが、ソフトウェア側の対応が必要です。
レジスタ規約
# システムコール番号:
x8 = syscall number (x86 の rax とは異なります!)
# 引数 (最大 6 個):
x0 = arg1 x1 = arg2 x2 = arg3
x3 = arg4 x4 = arg5 x5 = arg6
# 戻り値:
x0 = 戻り値 (>=0: 成功, -errno: エラー)
# ARM64 の syscall はレジスタを破壊しません (x0 を除く)
# → カーネルがすべてのレジスタを保存/復元します
# x86 の rcx/r11 の破壊とは異なります
svc 命令の動作
flowchart TD
SVC["svc #0 命令の実行"]
SVC --> S1["① PSTATE を保存 → SPSR_EL1"]
S1 --> S2["② 戻りアドレスを保存 → ELR_EL1<br/>(例外リンクレジスタ)"]
S2 --> S3["③ CPU を EL1 (カーネルモード) に切り替え"]
S3 --> S4["④ VBAR_EL1 + 0x400 にジャンプ<br/>(EL0 からの同期例外、64ビット)"]
S4 --> S5["⑤ カーネル例外ベクタ → kernel_entry<br/>→ el0_svc ハンドラ"]
classDef inst fill:#e3f2fd,stroke:#1565c0
classDef step fill:#f3e5f5,stroke:#7b1fa2
classDef done fill:#e8f5e9,stroke:#2e7d32
class SVC inst
class S1,S2,S3,S4 step
class S5 done
カーネルエントリ: el0_svc
// arch/arm64/kernel/entry.S
el0_svc:
// 1. すべてのユーザーレジスタをカーネルスタックに保存 (kernel_entry マクロ)
kernel_entry 0
// 2. x8 = syscall number を読み取る
ldr x16, [tsk, #TSK_TI_FLAGS]
tbnz x16, #TIF_SME, el0_sve_acc // SME をチェック
// 3. tracing かどうかをチェック (ptrace / audit)
ldr x16, [tsk, #TSK_TI_FLAGS]
tst x16, #_TIF_SYSCALL_WORK
b.ne el0_svc_naked
// 4. syscall ハンドラを呼び出す
bl el0_svc_common
→ invoke_syscall(regs->regs[8], regs->regs[0..5], ...)
→ sys_call_table[x8](x0, x1, x2, x3, x4, x5)
// 5. regs->regs[0] = 戻り値
// 6. kernel_exit 0 → すべてのレジスタを復元 → eret
x86-64 との主な違い
| x86-64 | ARM64 | |
|---|---|---|
| syscall 命令 | syscall | svc #0 |
| syscall nr レジスタ | rax | x8 |
| ハードウェア保存 | RCX←RIP, R11←RFLAGS | ELR_EL1, SPSR_EL1 |
| スタック切り替え | 手動 (per-CPU から読み取り) | 手動 (SP_EL0 から読み取り) |
| 戻り命令 | sysretq | eret |
| 破壊されるレジスタ | rcx, r11 | なし (すべて保存/復元) |
| 引数レジスタ | rdi,rsi,rdx,r10,r8,r9 | x0,x1,x2,x3,x4,x5 |
| 典型的なレイテンシ | ~50-70c | ~30-50c |
ARM64 固有の syscall
// ARM64 には x86 にない syscall がいくつかあります:
// getcpu (現在の CPU および NUMA ノードの取得):
; // 通常 vDSO を介して実装され、実際の syscall は行われません
// メモリモデル関連の syscall:
; // x86 にもありますが、ARM での実装は異なります
// PAC (ポインター認証):
__NR_prctl PR_PAC_RESET_KEYS // ARM64 で PAC キーをリセット
アトミック操作: LDXR / STXR
ARM64 はロック接頭辞 (x86 の LOCK CMPXCHG) に依存しません。
代わりに Load-Exclusive / Store-Exclusive ペアを使用します:
1. LDXR x0, [x1] // 読み取りおよび排他的アクセスとしてマーク
2. ... x0 を変更 ...
3. STXR w2, x0, [x1] // 書き込みを試みる → w2=0 で成功, w2=1 で失敗 (再試行)
4. cbnz w2, retry // 失敗 → 再試行
x86 の LOCK CMPXCHG との違い:
バスロックが必要ないため、複数のアドレスで同時に排他的アクセスが可能
大規模な NUMA システムでスケーラビリティに優れる
ユーザー空間での CPU 機能の取得
// ARM64: HWCAP フラグ (getauxval または /proc/self/auxv を介して)
unsigned long hwcap = ;
if
デバッグ
# システムコールテーブルの表示
# strace によるトレース
# HWCAP
LD_SHOW_AUXV=1 |
参考
- ソースコード:
arch/arm64/kernel/entry.S,arch/arm64/kernel/syscall.c - ARM64 ABI: AAPCS64 (Procedure Call Standard for ARM 64-bit)
- LWN: "System calls on ARM64", "Pointer Authentication in the kernel"
キーワード: svc, ARM64, AArch64, ELR_EL1, SPSR_EL1, LDXR/STXR, LSE, PAC, HWCAP, NEON