2026-09-04
You already know the vDSO turns clock_gettime() into a user-space memory read of a kernel-maintained page. What's less advertised is that the vDSO code is a conditional — it reads a clocksource identifier, and if the identifier isn't one it knows how to handle, it falls through to a real syscall instruction. Your "zero-syscall" clock call can silently become a 300ns syscall depending on what your kernel picked as the clocksource at boot.
The mechanism: the kernel exposes a vdso_data page containing the current time, a sequence counter (for lockless reads), a multiplier/shift for TSC-to-nanoseconds conversion, and a vclock_mode field. The vDSO's __vdso_clock_gettime switches on vclock_mode: VCLOCK_TSC reads RDTSC and does the math inline; VCLOCK_PVCLOCK (KVM paravirt) and VCLOCK_HVCLOCK (Hyper-V) have their own inline paths; VCLOCK_NONE means "I can't do this in user space" and jumps to the syscall fallback.
When does the kernel pick VCLOCK_NONE? Any time the current clocksource isn't safe to read from user space. Common triggers:
clocksource=hpet or tsc=unstable.Real-world example: A trading firm benchmarks clock_gettime(CLOCK_MONOTONIC) at 20ns on a dev box and deploys to a four-socket production server. Latency jumps to ~350ns per call — 17× slower. Cause: the four-socket box failed the TSC sync check at boot and fell back to HPET. Fix: echo tsc > /sys/devices/system/clocksource/*/current_clocksource after confirming synchronization, or boot with tsc=reliable.
Rule of thumb: before trusting vDSO performance, check cat /sys/devices/system/clocksource/clocksource0/current_clocksource. If it says tsc (or kvm-clock/hyperv_clocksource in a VM), you get the fast path. Anything else — hpet, acpi_pm, jiffies — and every call costs a syscall.
You can confirm the fallback empirically: strace -c ./your_program. If clock_gettime shows up in the syscall count at all, your vDSO isn't doing what you think it is.
