从 20 倍性能差距看 Linux 的 vDSO 与 vvar 机制

从 20 倍性能差距看 Linux 的 vDSO 与 vvar 机制

在学习操作系统时,我们或许都曾在 /proc/self/maps 的输出末尾瞥见过这两个神秘的内存段:[vvar][vdso]。课堂上它们往往被一笔带过------"这是为了减少系统调用开销设计的"。今天,就让我们深入探究这对搭档是如何在用户态优雅地"绕过"内核,实现性能飞跃的。

bash 复制代码
$ cat /proc/self/maps
...
7ffdd208d000-7ffdd2091000 r--p 00000000 00:00 0                          [vvar]
7ffdd2091000-7ffdd2093000 r-xp 00000000 00:00 0                          [vdso]
7ffdd1fe7000-7ffdd2008000 rw-p 00000000 00:00 0                          [stack]

场景引入:获取时间戳

我们以 gettimeofday 这个高频系统调用为例。在 Linux 中,获取当前时间是极为常见的操作------日志打印、性能计时、超时判断都依赖它。

方式一:通过 libc 调用

c 复制代码
#include <stdio.h>
#include <sys/time.h>

int main()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    printf("seconds: %ld\n", tv.tv_sec);
    return 0;
}

glibc 的 gettimeofday 实现非常聪明:它会优先检查 __vdso_gettimeofday 是否可用。如果可用,直接跳转到 vDSO 中的实现,全程用户态执行,不陷入内核;只有在 vDSO 不可用(例如某些旧内核或特定架构)时,才会回退到传统的 syscall 路径。

我们用 strace 验证一下是否真的避开了系统调用:

bash 复制代码
$ strace -e gettimeofday ./a.out
seconds: 1779362307
+++ exited with 0 +++

输出中没有 gettimeofday 系统调用的痕迹,证明它确实走了 vDSO 路径。

方式二:直接系统调用

作为对比,我们绕过 libc,直接用 syscall() 发起系统调用:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/syscall.h>

int sys_gettimeofday(struct timeval* tv, struct timezone* tz)
{
    return syscall(SYS_gettimeofday, tv, tz);
}

int main()
{
    struct timeval tv;
    sys_gettimeofday(&tv, NULL);
    printf("seconds: %ld\n", tv.tv_sec);
    return 0;
}
bash 复制代码
$ strace -e gettimeofday ./a.out
gettimeofday({tv_sec=1779362323, tv_usec=339161}, NULL) = 0
seconds: 1779362323
+++ exited with 0 +++

这次 strace 清晰地捕捉到了 gettimeofday 系统调用。

性能对比:100 万次调用

将以上两种方案各执行 100 万次,结果令人震惊:

bash 复制代码
$ time ./libc_time     # 走 vDSO

________________________________________________________
Executed in   27.79 millis    fish           external
   usr time   27.35 millis    0.00 micros   27.35 millis
   sys time    0.44 millis  438.00 micros    0.00 millis

$ time ./syscall_time  # 走 syscall

________________________________________________________
Executed in  556.01 millis    fish           external
   usr time  326.03 millis  323.00 micros  325.71 millis
   sys time  226.55 millis  147.00 micros  226.41 millis

直接系统调用版本耗时约为 vDSO 版本的 20 倍!

差距主要来自两个方面:

  1. 上下文切换 :每次 syscall 都会触发特权级切换(用户态 → 内核态 → 用户态),涉及寄存器保存、页表切换、TLB 刷新等开销。
  2. 内核路径执行:进入内核后,需要经过系统调用分发、参数校验、VDSO 数据读取等一系列流程。

原理解析:内核如何把代码"借"给用户态

vDSO(virtual Dynamic Shared Object)和 vvar(virtual VARiables)的本质,是内核在创建进程时,将一小部分只读的代码和数据映射到用户空间的特定区域:

属性 作用
[vdso] r-xp(可读、可执行) 存放可由用户态直接执行的内核代码
[vvar] r--p(只读) 存放内核维护的、对用户态可见的变量

gettimeofday 为例,内核中的实现逻辑可以简化为:

c 复制代码
void vdso_gettimeofday(struct timeval* tv, struct timezone* tz)
{
    // 直接从 vvar 映射的内存读取时间
    tv->tv_sec = vvar_page->wall_time_sec;
    tv->tv_usec = vvar_page->wall_time_nsec / 1000;
}

内核初始化时会:

  1. vdso_gettimeofday 等函数的机器码写入 [vdso]
  2. wall_time_secwall_time_nsec 等变量映射到 [vvar]
  3. 每次时钟中断或定时更新时,内核更新 [vvar] 中的变量值

于是用户态进程调用 gettimeofday 时,实际只是在执行自己地址空间中的一小段代码,从同地址空间的只读内存中取值------没有陷入内核,没有上下文切换,就像调用普通函数一样快。

哪些系统调用享受了 vDSO 待遇?

并非所有系统调用都适合 vDSO 化。只有满足以下条件的调用才会被优化:

  • 只读操作:不会修改进程状态或内核数据结构
  • 数据由内核维护:例如时间、CPU 信息、进程 ID 等
  • 对一致性要求可控:允许极短暂的数据延迟(纳秒级)

目前 Linux vDSO 通常包含以下函数(具体取决于架构和内核版本):

  • __vdso_clock_gettime
  • __vdso_gettimeofday
  • __vdso_time
  • __vdso_getcpu
  • __vdso_clock_getres

小结

vDSO 和 vvar 是 Linux 内核向用户态伸出的"友好之手"------它用极低的成本(几个内存页),为高频率、只读型的系统调用开辟了一条"高速公路"。

对于开发者而言,好消息是:只要你通过 glibc 调用 gettimeofdayclock_gettime 等函数,就已经在享受这一优化了,无需额外操作。但理解其背后的原理,能帮助我们在编写高性能程序时,更清楚地知道"时间从哪来",以及为什么某些操作比其他操作"更轻"。

下次再看到 /proc/self/maps 中的 [vdso][vvar],希望你会心一笑:那是内核留给用户态的一份精心准备的礼物。

相关推荐
Bruce_kaizy41 分钟前
c++ linux环境编程——文件io介绍以及open 、write 、read 三剑客深度详解
linux·服务器·c++·ubuntu·操作系统·文件io
iCxhust13 小时前
8086 汇编 TINY 和 SMALL 编程MODEL区别
汇编·单片机·嵌入式硬件·操作系统·微机原理·8088单板机
atomicmaker1 天前
经典同步问题
操作系统·pv·同步问题
iCxhust1 天前
Proteus例程导入方法
操作系统·proteus·课程设计·微机原理·8086最小系统·8088单板机
空荡forevere1 天前
Linux文件系统(三)
linux·运维·系统架构·操作系统
atomicmaker2 天前
处理器管理
操作系统·进程·同步与互斥·cpu调度·处理器管理
kunge20133 天前
1. Tmux 使用指南(入门篇)
后端·架构·操作系统
iCxhust3 天前
AD0808调试笔记
笔记·单片机·嵌入式硬件·操作系统·微机原理·8088单板机
fakerth6 天前
【OpenHarmony】startup_init 模块
操作系统·openharmony