📘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 给用户端