本页目录

glibc syscall 包装剖析

覆盖: glibc syscall wrapper → INLINE_SYSCALL → vsyscall/vDSO → gettimeofday 快路径 → 系统调用延迟 → 自写 syscall 封装 适用: glibc 2.x, 对比 musl, x86-64 为主

概述

用户态代码不会直接写 syscall——glibc 在每个系统调用外封装了一层。这层封装处理了:参数传递(寄存器安排)、返回值到 errno 的转换、取消点(cancellation point)、以及 vDSO 快速路径。理解这一层可以解释"为什么 clock_gettime 比 getpid 快"或者"为什么自写的 syscall 封装有时更快"。

glibc: INLINE_SYSCALL 宏

// sysdeps/unix/sysv/linux/x86_64/sysdep.h
// glibc 系统调用的封装层次:

// 最底层: 汇编宏
#define INTERNAL_SYSCALL(name, nr, args...)                \
({                                                          \
    unsigned long int resultvar;                            \
    LOAD_ARGS_##nr(args)          /* 把参数放入寄存器 */    \
    LOAD_REGS_##nr                 /* 加载 rdi,rsi,rdx,... */ \
    register long int _a1 asm ("rdi");                      \
    asm volatile (                                          \
        "syscall\n\t"                                       \
        : "=a" (resultvar)                                  \
        : "0" (__NR_##name) ASM_ARGS_##nr                   \
        : "memory", "cc", "r11", "cx");                     \
    (long int) resultvar;                                   \
})

// 应用层: 转 errno
#define INLINE_SYSCALL(name, nr, args...)                  \
({                                                          \
    long int sc_ret = INTERNAL_SYSCALL(name, nr, args);    \
    if (__glibc_unlikely(INTERNAL_SYSCALL_ERROR_P(sc_ret))) \
    {                                                       \
        __set_errno(INTERNAL_SYSCALL_ERRNO(sc_ret));       \
        sc_ret = -1;                                        \
    }                                                       \
    sc_ret;                                                 \
})

关键细节:

  • asm volatile: 防止编译器跨 syscall 重排内存访问("memory" clobber)
  • "cc" clobber: syscall 可能改标志寄存器
  • "r11", "cx" clobber: syscall 指令破坏这两个寄存器
  • 返回值检查: 用 __glibc_unlikely 分支预测 errno 路径不常走

gettimeofday / clock_gettime: vDSO 快路径

// glibc 的 clock_gettime 实现 (简化):
int __clock_gettime64(clockid_t clock_id, struct __timespec64 *tp)
{
    // 尝试 vDSO 快路径 (用户态执行, 不 syscall)
    if (__glibc_likely(clock_id < CLOCK_THREAD_CPUTIME_ID))
    {
        __vdso_clock_gettime_func vdso_func =
            GLRO(dl_vdso_clock_gettime);  // 启动时从 vDSO 获取

        if (vdso_func != NULL)
        {
            int ret = vdso_func(clock_id, tp);
            if (ret == 0) return 0;
        }
    }
    // vDSO 不可用 → 回退到 syscall
    return INLINE_SYSCALL(clock_gettime64, 2, clock_id, tp);
}

// vDSO 版本 (arch/x86/entry/vdso/vclock_gettime.c):
//   1. 读 TSC (rdtsc, ~10c)
//   2. 应用 timekeeper 转换 (mult + shift, ~20c)
//   3. 检查 seqlock (read_seqbegin/read_seqretry, ~5c)
//   → 总计 ~35c, vs ~70c syscall → 快 ~2x

glibc vs musl: syscall 封装差异

glibcmusl
封装复杂度多层宏 (INLINE_SYSCALL → INTERNAL_SYSCALL)单层内联 asm
errno 处理TLS 函数调用内联直接写 __errno_location()
取消点 (cancellation)在 read/write 等"慢" syscall 前检查更简单的模型
vDSO启动时初始化 + 缓存同上,但更轻量
代码大小~50 行/syscall (带所有宏展开)~5 行/syscall

系统调用延迟分析

# 测量 syscall 延迟 (最小 0-arg syscall):
# getpid() → syscall 39 (x86-64, getpid)
#   缓存了 PID → 直接返回 → 不会真的去读 PCB

# syscall 延迟测量工具: libMicro, lmbench
典型延迟 (3.5GHz x86-64):
  getpid (cached):                ~70ns (~250c)
  getppid:                        ~90ns
  gettimeofday (vDSO):            ~15ns (~50c)  ← 极快!
  gettimeofday (syscall fallback): ~80ns
  write(1 byte to /dev/null):     ~200ns
  read(1 byte from /dev/zero):    ~200ns
  futex(FUTEX_WAIT, uncontended): ~150ns
  futex(FUTEX_WAIT, contended):   ~5-20μs (包括上下文切换)

延迟组成:
  syscall 指令:                 ~20c
  内核入口/出口 (swapgs + 栈切): ~20c
  实际 syscall 逻辑:             ~30c+
  Spectre/Meltdown 防护:         ~20c (KPTI on affected CPUs)

手写 syscall: 更快的封装

// glibc 的 read() 有一定开销 (errno 转换等)
// 如果你在热路径上且自己能处理错误 → 手动写 syscall:

static inline long __read(int fd, void *buf, size_t count) {
    long ret;
    register long rdi asm("rdi") = fd;
    register long rsi asm("rsi") = (long)buf;
    register long rdx asm("rdx") = count;
    register long rax asm("rax") = __NR_read;
    asm volatile("syscall"
                 : "=a"(ret)
                 : "r"(rdi), "r"(rsi), "r"(rdx), "a"(rax)
                 : "rcx", "r11", "memory");
    return ret;  // 负数 = -errno
}

// 何时值得?
//   - io_uring 热路径 → 已经包装了, 不需要
//   - 自定义 allocator → 可能需要 mmap wrapper
//   - 性能基准测试 → 排除 glibc 开销

参考

  • 源码⁠: glibc sysdeps/unix/sysv/linux/x86_64/sysdep.h, sysdeps/unix/sysv/linux/clock_gettime.c, musl arch/x86_64/syscall_arch.h
  • LWN: "vDSO and clock_gettime", "System call overhead"
  • 工具⁠: man syscalls, strace -c (syscall count/time)

关键词: INLINE_SYSCALL, syscall wrapper, vDSO, clock_gettime, errno, cancellation point, glibc, musl, syscall latency