On this page
Anatomy of glibc Syscall Wrappers
Coverage: glibc syscall wrapper → INLINE_SYSCALL → vsyscall/vDSO → gettimeofday fast path → syscall latency → custom syscall wrapper Applicable to: glibc 2.x, with comparisons to musl, primarily on x86-64
Overview
User-space code does not directly emit the syscall instruction; glibc wraps every system call with a layer of abstraction. This wrapper layer handles: argument passing (register allocation), return value to errno conversion, cancellation points, and the vDSO fast path. Understanding this layer explains why "clock_gettime is faster than getpid" or why "a custom syscall wrapper can sometimes be faster."
glibc: The INLINE_SYSCALL Macro
// sysdeps/unix/sysv/linux/x86_64/sysdep.h
// glibc syscall wrapper hierarchy:
// Lowest level: Assembly macro
// Application layer: Convert to errno
Key details:
asm volatile: Prevents the compiler from reordering memory accesses across the syscall (via the"memory"clobber)."cc"clobber: The syscall instruction may modify the flags register."r11", "cx"clobber: The syscall instruction destroys these two registers.- Return value check: Uses
__glibc_unlikelybranch prediction to indicate that the errno path is rarely taken.
gettimeofday / clock_gettime: vDSO Fast Path
// glibc's clock_gettime implementation (simplified):
int
// vDSO version (arch/x86/entry/vdso/vclock_gettime.c):
// 1. Read TSC (rdtsc, ~10 cycles)
// 2. Apply timekeeper conversion (mult + shift, ~20 cycles)
// 3. Check seqlock (read_seqbegin/read_seqretry, ~5 cycles)
// → Total ~35 cycles, vs ~70 cycles for syscall → ~2x faster
glibc vs musl: Syscall Wrapper Differences
| glibc | musl | |
|---|---|---|
| Wrapper Complexity | Multi-level macros (INLINE_SYSCALL → INTERNAL_SYSCALL) | Single-level inline assembly |
| errno Handling | TLS function call | Inline direct write to __errno_location() |
| Cancellation Points | Checks before "slow" syscalls like read/write | Simpler model |
| vDSO | Initialized and cached at startup | Same, but lighter weight |
| Code Size | ~50 lines/syscall (with all macro expansions) | ~5 lines/syscall |
Syscall Latency Analysis
# Measure syscall latency (minimum 0-arg syscall):
# getpid() → syscall 39 (x86-64, getpid)
# PID is cached → returns directly → does not actually read the PCB
# Syscall latency measurement tools: libMicro, lmbench
Typical Latency (3.5GHz x86-64):
getpid (cached): ~70ns (~250 cycles)
getppid: ~90ns
gettimeofday (vDSO): ~15ns (~50 cycles) ← Extremely fast!
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 (including context switch)
Latency Breakdown:
syscall instruction: ~20 cycles
Kernel entry/exit (swapgs + stack switch): ~20 cycles
Actual syscall logic: ~30+ cycles
Spectre/Meltdown Mitigations: ~20 cycles (KPTI on affected CPUs)
Hand-written Syscall: A Faster Wrapper
// glibc's read() has some overhead (errno conversion, etc.)
// If you are on a hot path and can handle errors yourself → write the syscall manually:
static inline long
// When is it worth it?
// - io_uring hot path → already wrapped, not needed
// - Custom allocator → might need an mmap wrapper
// - Performance benchmarking → to exclude glibc overhead
References
- Source Code: glibc
sysdeps/unix/sysv/linux/x86_64/sysdep.h,sysdeps/unix/sysv/linux/clock_gettime.c, muslarch/x86_64/syscall_arch.h - LWN: "vDSO and clock_gettime", "System call overhead"
- Tools:
man syscalls, strace -c (syscall count/time)
Keywords: INLINE_SYSCALL, syscall wrapper, vDSO, clock_gettime, errno, cancellation point, glibc, musl, syscall latency