本页目录

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-64ARM64
syscall 指令syscallsvc #0
syscall nr 寄存器raxx8
硬件保存RCX←RIP, R11←RFLAGSELR_EL1, SPSR_EL1
栈切换手动 (读 per-CPU)手动 (读 SP_EL0)
返回指令sysretqeret
clobbered 寄存器rcx, r11无 (全保存/恢复)
参数寄存器rdi,rsi,rdx,r10,r8,r9x0,x1,x2,x3,x4,x5
典型延迟~50-70c~30-50c

ARM64 特有 syscall

// ARM64 有一些 x86 没有的 syscall:

// getcpu (获取当前 CPU 和 NUMA node):
getcpu(&cpu, &node, NULL);  // 通常通过 vDSO 实现,不真的 syscall

// 内存模型相关的 syscall:
mlock2(start, len, MLOCK_ONFAULT);  // 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)
#define HWCAP_FP       (1 << 0)   // 浮点
#define HWCAP_ASIMD    (1 << 1)   // NEON/ASIMD
#define HWCAP_AES      (1 << 3)   // AES 指令
#define HWCAP_PMULL    (1 << 4)   // 多项式乘法 (GCM)
#define HWCAP_SHA1     (1 << 5)
#define HWCAP_SHA2     (1 << 6)
#define HWCAP_CRC32    (1 << 7)   // CRC32 指令
#define HWCAP_ATOMICS  (1 << 8)   // LSE 原子操作 (ARMv8.1)
#define HWCAP_SVE      (1 << 22)  // Scalable Vector Extension

unsigned long hwcap = getauxval(AT_HWCAP);
if (hwcap & HWCAP_AES) { /* use AES instructions */ }

调试

# 查看 syscall 表
cat /usr/include/asm-generic/unistd.h  # 通用表
cat /usr/include/asm/unistd.h          # ARM64 特有

# strace 追踪
strace -e trace=write,read ./a.out

# HWCAP
LD_SHOW_AUXV=1 /bin/true | grep HWCAP

参考

  • 源码⁠: 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