glibc 将 linux 系统调用封装成更加友好的接口, 方便用户使用. 以最常用的系统调用 open 举例, 当我们在用户态进程里调用 open 函数, 大部分情况下我们调用的都是来自 glibc 里的 open 函数.
这个函数是如何定义的呢?
int open(const char *pathname, int flags, mode_t mode)
在 glibc 的源代码中,有个文件 syscalls.list,里面列着所有 glibc 的函数对应的系统调用,就像下面这个样子:
# File name Caller Syscall name Args Strong name Weak names
open - open Ci:siv __libc_open __open open
另外,glibc 还有一个脚本 make-syscall.sh,可以根据上面的配置文件,对于每一个封装好的系统调用,生成一个文件。这个文件里面定义了一些宏,例如 #define SYSCALL_NAME open
。
glibc 还有一个文件 syscall-template.S,使用上面这个宏,定义了这个系统调用的调用方式。
T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
ret
T_PSEUDO_END (SYSCALL_SYMBOL)
#define T_PSEUDO(SYMBOL, NAME, N) PSEUDO (SYMBOL, NAME, N)
这里的 PSEUDO 也是一个宏,它的定义如下:
#define PSEUDO(name, syscall_name, args) \\
.text; \\
ENTRY (name) \\
DO_CALL (syscall_name, args); \\
cmpl $-4095, %eax; \\
jae SYSCALL_ERROR_LABEL
里面对于任何一个系统调用,会调用 DO_CALL。这也是一个宏,这个宏 32 位和 64 位的定义是不一样的。