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 历史;说明为什么 Li…
CPU 调度:CFS 的虚拟运行时间与 EEVDF 的虚拟截止期
从比例份额调度的论文谱系出发,对照 Linux v6.6 与 v6.12 fair.c 源码,解释 CFS 的 vruntime、EEVDF 的 lag/eligible/deadline,并用确定性模拟复现延迟与 lag 的差异。
I/O 调度:在寻道、队列深度与公平性之间取舍
从电梯算法到 blk-mq,解释 Linux I/O 调度器为何在 HDD 上排序、在共享设备上保公平、在 NVMe 上常选择 none,并用确定性模拟展示寻道距离与队列尾延迟的取舍。