📘AArch64 下 glibc `open()` 调用到内核系统调用入口的全路径解析

📘AArch64 下 glibc open() 调用到内核系统调用入口的全路径解析

目标

本文档对 AArch64 (即 ARM64) 架构下从 glibc 中的 open() 函数调用至 Linux 内核中 sys_openat() 系统调用入口的全路径进行全面、精精、严密的分析。选用 AArch64 架构是因为其在嵌入式、手机、服务器等领域应用很广,宜于全面理解 Linux syscall 机制。


一、从 glibc open() 函数调用开始

int fd = open("/tmp/a.txt", O_RDONLY);

open() 是 POSIX API,glibc 中实际是一层展开:

#define open(...) SYSCALL_CANCEL(openat, AT_FDCWD, __VA_ARGS__)

即使用 openat() 实现。


二、glibc syscall 展开层层结构

以 5 个参数为例:

1. 调用链进程

SYSCALL_CANCEL(openat, AT_FDCWD, file, flags, mode)

展开层层如下:

SYSCALL_CANCEL(...)                
  INLINE_SYSCALL_CALL(...)            
  __INLINE_SYSCALL_DISP(__INLINE_SYSCALL, ...)
  __SYSCALL_CONCAT(__INLINE_SYSCALL, N)(...)
  __INLINE_SYSCALL5(name, a1, a2, a3, a4, a5)
  INLINE_SYSCALL(name, 5, a1, a2, a3, a4, a5)

2. INLINE_SYSCALL 实现

#define INLINE_SYSCALL(name, nr, args...) \
({ \
  long int sc_ret = INTERNAL_SYSCALL(name, nr, args); \
  __glibc_unlikely(INTERNAL_SYSCALL_ERROR_P(sc_ret)) \
    ? SYSCALL_ERROR_LABEL(INTERNAL_SYSCALL_ERRNO(sc_ret)) \
    : sc_ret; \
})

三、INTERNAL_SYSCALL 到 svc #0 入核

1. 实际定义分部

sysdeps/unix/sysv/linux/aarch64/sysdep.h 中:

#define INTERNAL_SYSCALL(name, nr, args...) \
  INTERNAL_SYSCALL_RAW(__NR_##name, nr, args)

2. INTERNAL_SYSCALL_RAW 核心实现

#define INTERNAL_SYSCALL_RAW(name, nr, args...)            \
({ long _sys_result;                                       \
   {                                                       \
     LOAD_ARGS_##nr (args)                                 \
     register long _x8 asm ("x8") = (name);                \
     asm volatile (                                        \
       "svc 0  // syscall " #name                          \
       : "=r" (_x0)                                        \
       : "r"(_x8) ASM_ARGS_##nr                           \
       : "memory");                                        \
     _sys_result = _x0;                                    \
   }                                                       \
   _sys_result; })

3. 参数装入

register long _x0 asm("x0") = arg1; // fd
register long _x1 asm("x1") = arg2; // path
register long _x2 asm("x2") = arg3; // flags
register long _x3 asm("x3") = arg4; // mode
register long _x4 asm("x4") = arg5; // reserve
register long _x8 asm("x8") = __NR_openat;

4. 结果

执行指令:

svc #0

四、内核端调用扩展

1. 输入异常向量 entry.S

文件:arch/arm64/kernel/entry.S

el0_sync:
    kernel_entry 0
    bl el0_sync_handler

2. el0_sync_handler()

文件:arch/arm64/kernel/syscall.c

void el0_sync_handler(struct pt_regs *regs) {
    ...
    syscall_handler(regs);
}

3. syscall_handler()

unsigned long scno = regs->regs[8]; // x8 是 syscall 号
sys_call_table[scno]  sys_openat(...)

4. sys_openat()

在内核 fs 层:

SYSCALL_DEFINE4(openat, int dfd, const char __user *filename,
                         int flags, umode_t mode)
{
    return do_sys_openat2(dfd, filename, flags, mode);
}

经由 VFS 层扩展到文件系统,最终返回 fd 或错误码


五、完整调用链流程图

glibc:
    open(path, flags, mode)
      → SYSCALL_CANCEL(openat, AT_FDCWD, ...)
      → __INLINE_SYSCALL5(...)
      → INLINE_SYSCALL(...)
      → INTERNAL_SYSCALL(...)
      → INTERNAL_SYSCALL_RAW(...)
        → 参数装入到 x0~x5, x8
        → svc #0

kernel:
    svc #0
      → entry.S: el0_sync
        → el0_sync_handler()
          → syscall_handler()
            → sys_call_table[__NR_openat]
              → sys_openat(...)
                → do_sys_openat2()
                  → VFS
                    → file system

附录: 系统调用 ABI 规则 (AArch64)

参数 存储位 说明
syscall# x8 系统调用编号
arg1 x0 第一个参数
arg2 x1 第二个参数
arg3 x2 第三个参数
arg4 x3 第四个参数
arg5 x4 第五个参数
arg6 x5 第六个参数
return x0 系统调用返回值

结论

  • glibc 通过多层 macro 抽象,最终进入 INTERNAL_SYSCALL_RAW 模板
  • 参数装入到 x0~x5,系统调用编号装入 x8
  • svc #0 触发输入异常
  • 内核通过 sys_call_table 分发到 sys_openat()
  • 并最终返回 fd 给用户端