---
title: glibc syscall 包装剖析
url: https://doc.liz6.com/systems-programming/02-system-call-abi/03-anatomy-of-glibc-syscall-wrappers
locale: zh
area: systems-programming
tags:
- systems-programming
- 系统调用ABI
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: glibc syscall wrapper → INLINE_SYSCALL → vsyscall/vDSO → gettimeofday 快路径 → 系统调用延迟 → 自写 syscall 封装 适用: glibc 2.x, 对比 musl, x86-64 为主'
---

# 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 宏

```c
// 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 快路径

```c
// 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 封装差异

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

## 系统调用延迟分析

```bash
# 测量 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: 更快的封装

```c
// 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*
