gettimeofday()
是调用频率最高的系统调用之一。如果每次都进内核 → 200ns
开销。vDSO 让它在用户态完成 → 20ns。
一、先看图
flowchart LR
APP[用户程序] -->|调用| VDSO[vDSO<br/>linux-vdso.so.1]
VDSO -->|读| VVAR[vvar 页<br/>内核维护的只读数据]
VVAR --> TSC[读 TSC<br/>计算时间]
APP2[传统方式] -->|syscall| KERNEL[内核<br/>sys_clock_gettime]
classDef fast fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef slow fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class APP,VDSO,VVAR,TSC fast
class APP2,KERNEL slow
二、什么是 vDSO
Virtual Dynamic Shared Object:内核编译的一小段共享库 → mmap 到每个进程地址空间。
cat /proc/self/maps | grep vdso
# 7fff... r-xp [vdso]
cat /proc/self/maps | grep vvar
# 7fff... r--p [vvar]- vDSO:可执行代码(
__vdso_clock_gettime等函数) - vvar:内核定期更新的只读数据(时间信息)
三、支持的函数
| 函数 | x86_64 | arm64 |
|---|---|---|
| clock_gettime | ✓ | ✓ |
| gettimeofday | ✓ | ✓ |
| clock_getres | ✓ | ✓ |
| time | ✓ | — |
| getcpu | ✓ | — |
| getrandom(6.11+) | ✓ | ✓ |
四、工作原理
4.1 时间读取
// vDSO 内部(简化)
int __vdso_clock_gettime(clockid_t clk, struct timespec *ts)
{
struct vdso_data *vd = __arch_get_vdso_data();
// seqcount 重试循环
do {
seq = vdso_read_begin(vd);
ns = vd->clock_mode == VDSO_CLOCKMODE_TSC
? rdtsc() * vd->mult >> vd->shift + vd->base_ns
: fallback_to_syscall();
} while (vdso_read_retry(vd, seq));
ts->tv_sec = ns / NSEC_PER_SEC;
ts->tv_nsec = ns % NSEC_PER_SEC;
}内核每 tick 更新 vvar 中的时间基准 → vDSO 读 TSC 计算偏移。
4.2 Fallback
如果 clocksource 不适合 vDSO(如 HPET)→ vDSO 退回 syscall。
五、vsyscall(已退役)
vsyscall 是 vDSO
的前身——固定地址(0xffffffffff600000)→
安全风险(ASLR 无效)。
现代内核:vsyscall=emulate(陷入内核模拟)或
vsyscall=none(禁用)。
六、getrandom vDSO(6.11+)
getrandom(buf, 32, 0); // 传统:syscall
// vDSO 版本:用户态生成随机数内核维护 ChaCha20 状态在 vvar → vDSO 在用户态生成随机数 → 性能大幅提升。
七、time namespace
unshare(CLONE_NEWTIME);
// 写 /proc/self/timens_offsets每个 time namespace 有独立的 vvar → vDSO 读到的时间带偏移 → 容器迁移时保持时间连续性。
八、性能对比
| 方式 | clock_gettime 延迟 |
|---|---|
| syscall | ~200ns |
| vDSO (TSC) | ~15-25ns |
高频调用场景(数据库、交易系统、日志时间戳)→ 10 倍差距。
九、观察
# 查看 vDSO
ldd /bin/ls | grep vdso
# linux-vdso.so.1
# 导出 vDSO
dd if=/proc/self/mem of=vdso.so skip=$(cat /proc/self/maps | grep vdso | cut -d- -f1) bs=1 count=4096
# 验证是否走 vDSO
strace -e clock_gettime ./my_app # 应该看不到 syscall(vDSO 不经过 syscall)
ltrace -e clock_gettime ./my_app # ltrace 可以看到十、小结
- vDSO = 内核编译的用户态共享库 → 零 syscall 读时间
- vvar 页内核更新、用户只读
- seqcount 保证一致性
- getrandom vDSO 是最新扩展
- vsyscall 已退役 → vDSO 是唯一正确方式
参考文献
arch/x86/entry/vdso/Documentation/vDSO/man 7 vdso- Jason Donenfeld, “getrandom() vDSO.” 2024
工具
lddltrace/proc/self/maps
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】时钟源
TSC 真的稳吗?本文讲 clocksource/clockevents 分离、TSC invariant/unstable 判定、HPET/ACPI PM/arch_timer、watchdog 校准、vDSO gettimeofday、PTP/PHC 硬件时间戳。
【操作系统百科】系统调用 ABI:x86_64 / arm64 / riscv / Windows NT 对照
系统调用是 OS 最稳定的接口。本文拆解 Linux syscall 的参数寄存器约定、返回值规范(负 errno 与 2-value ABI)、x86_64 SYSCALL、arm64 SVC、RISC-V ECALL、Windows NT 的 int 2e/syscall/SYSENTER 历史;说明为什么 Linux 承诺 \"don't break userspace\"、什么东西算 syscall ABI、vDSO 如何用共享内存加速、Go/musl/glibc 各自怎么实现 syscall stub。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。