本页目录
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 = return value (>=0: success, -errno: error)
# ARM64 的 syscall 不破坏任何寄存器 (除了 x0)
# → 内核保存/恢复所有寄存器
# 与 x86 的 rcx/r11 clobber 不同
svc 指令行为
flowchart TD
SVC["svc #0 指令执行"]
SVC --> S1["① 保存 PSTATE → SPSR_EL1"]
S1 --> S2["② 保存返回地址 → ELR_EL1<br/>(Exception Link Register)"]
S2 --> S3["③ CPU 切换到 EL1 (内核态)"]
S3 --> S4["④ 跳转到 VBAR_EL1 + 0x400<br/>(sync exception from EL0, 64-bit)"]
S4 --> S5["⑤ 内核异常向量 → kernel_entry<br/>→ el0_svc handler"]
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. 检查是否 trace (ptrace / audit)
ldr x16, [tsk, #TSK_TI_FLAGS]
tst x16, #_TIF_SYSCALL_WORK
b.ne el0_svc_naked
// 4. 调用 syscall handler
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 |
| clobbered 寄存器 | 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 node):
; // 通常通过 vDSO 实现,不真的 syscall
// 内存模型相关的 syscall:
; // x86 也有,ARM 实现不同
// PAC (Pointer Authentication):
__NR_prctl PR_PAC_RESET_KEYS // ARM64 重置 PAC 密钥
原子操作: LDXR / STXR
ARM64 不依赖 lock prefix (x86 的 LOCK CMPXCHG)
而是使用 Load-Exclusive / Store-Exclusive 对:
1. LDXR x0, [x1] // 加载并标记为 exclusive
2. ... modify x0 ...
3. STXR w2, x0, [x1] // 尝试存储 → w2=0 成功, w2=1 失败(重试)
4. cbnz w2, retry // 失败 → 重试
与 x86 的 LOCK CMPXCHG 不同:
不需要锁总线 → 可在多个地址上同时有 exclusive access
大型 NUMA 系统上扩展性更好
用户态获取 CPU 特性
// ARM64: HWCAP flags (via getauxval or /proc/self/auxv)
unsigned long hwcap = ;
if
调试
# 查看 syscall 表
# 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