Linux vdso机制

一、简介

Linux中的vdso(Virtual Dynamic Shared Object)是一种特殊的动态共享对象,它在用户空间和内核空间之间提供了一种高效的接口。vdso机制的目的是减少用户空间程序与内核之间频繁的上下文切换开销,提高系统性能。

“vDSO”(虚拟动态共享对象)是一个小型的共享库,内核会自动将其映射到所有用户空间应用程序的地址空间中。应用程序通常无需关注这些细节,因为vDSO最常由C库调用。这样,可以以正常方式编码,使用标准函数,而C库会负责使用通过vDSO可用的任何功能。

vDSO的存在是为什么?内核提供了一些系统调用,用户空间代码经常使用这些调用,以至于这些调用可能主导整体性能。这既是由于调用的频率,又是由于从用户空间退出并进入内核所产生的上下文切换开销。

vdso包含一组特定的函数,这些函数在用户空间中执行,但其实现是由内核提供的。用户空间程序可以通过调用这些函数来访问一些系统功能,而无需陷入内核态。

vdso的一个重要用途是实现系统调用的快速路径。当用户空间程序执行系统调用时,通常需要进行一次上下文切换,将控制权从用户态切换到内核态。然而,某些系统调用是非常频繁且开销较小的,这种上下文切换的开销可能会成为性能瓶颈。vdso提供了一个快速路径,通过在用户空间中执行特定的系统调用函数,避免了不必要的上下文切换,从而提高了系统调用的性能。

在Linux中,vdso通常以linux-vdso.so.X的形式存在于/proc/self/maps中,并且被映射到每个进程的地址空间中。这样,用户空间程序可以直接调用vdso中的函数,而无需显式加载和链接vdso库。

总结来说,vdso是Linux中用于优化系统调用性能的一种机制,它提供了一组在用户空间执行的特定系统调用函数,以减少用户态和内核态之间的上下文切换开销,并提高系统性能。

备注:
vdso只包括了几个特定的系统调用:

clock_gettime
gettimeofday
getcpu
time
clock_getres

比如gettimeofday:
一个经常被使用的系统调用是gettimeofday(2)。这个系统调用既可以被用户空间应用程序直接调用,也可以被C库间接调用。想象一下时间戳、定时循环或轮询,所有这些都经常需要知道当前的时间。这些信息也不是机密的,任何特权模式(root或非特权用户)的应用程序都会得到相同的答案。因此,内核会安排将回答这个问题所需的信息放置在进程可以访问的内存中。现在,调用gettimeofday(2)变成了一个普通的函数调用和几次内存访问。

二、vdso

2.1 用户态

# ldd /usr/bin/ls
        linux-vdso.so.1 (0x00007ffca7b40000)
        ......
# cat /proc/1/maps
55637a23d000-55637a26f000 r--p 00000000 08:05 59514907                   /usr/lib/systemd/systemd
55637a26f000-55637a32d000 r-xp 00032000 08:05 59514907                   /usr/lib/systemd/systemd
55637a32d000-55637a383000 r--p 000f0000 08:05 59514907                   /usr/lib/systemd/systemd
55637a383000-55637a3c9000 r--p 00145000 08:05 59514907                   /usr/lib/systemd/systemd
55637a3c9000-55637a3ca000 rw-p 0018b000 08:05 59514907                   /usr/lib/systemd/systemd
55637c14a000-55637c416000 rw-p 00000000 00:00 0                          [heap]

......

7f008b2bf000-7f008b2c0000 rw-p 00000000 00:00 0
7ffd3bc40000-7ffd3bd42000 rw-p 00000000 00:00 0                          [stack]
7ffd3bd4f000-7ffd3bd53000 r--p 00000000 00:00 0                          [vvar]
7ffd3bd53000-7ffd3bd55000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

其中:

7ffd3bd53000-7ffd3bd55000 r-xp 00000000 00:00 0                          [vdso]

可以看到vdso内存大小:0x2000 = 4096 * 2,即两个虚拟页面的大小。

vdso的起始虚拟地址在进程1是:0x7ffd3bd53000,转化为十进制即140725607280640,将这段内存dump到文件中:

# dd if=/proc/1/mem of=/tmp/linux-vdso.so skip=140725607280640 ibs=1 count=8192
dd: /proc/1/mem: cannot skip to specified offset
8192+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00971912 s, 843 kB/s

由于vDSO是一个完整的ELF镜像,可以对其进行符号查找:

# objdump -T /tmp/linux-vdso.so

/tmp/linux-vdso.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000a10  w   DF .text  0000000000000413  LINUX_2.6   clock_gettime
0000000000000690 g    DF .text  0000000000000348  LINUX_2.6   __vdso_gettimeofday
0000000000000e30  w   DF .text  0000000000000060  LINUX_2.6   clock_getres
0000000000000e30 g    DF .text  0000000000000060  LINUX_2.6   __vdso_clock_getres
0000000000000690  w   DF .text  0000000000000348  LINUX_2.6   gettimeofday
00000000000009e0 g    DF .text  0000000000000029  LINUX_2.6   __vdso_time
0000000000000ec0 g    DF .text  000000000000009c  LINUX_2.6   __vdso_sgx_enter_enclave
00000000000009e0  w   DF .text  0000000000000029  LINUX_2.6   time
0000000000000a10 g    DF .text  0000000000000413  LINUX_2.6   __vdso_clock_gettime
0000000000000000 g    DO *ABS*  0000000000000000  LINUX_2.6   LINUX_2.6
0000000000000e90 g    DF .text  0000000000000025  LINUX_2.6   __vdso_getcpu
0000000000000e90  w   DF .text  0000000000000025  LINUX_2.6   getcp

2.2 内核态

vDSO 会向用户提供的 syscall:

// linux-5.13/arch/x86/entry/vdso/vdso.lds.S

/*
 * This controls what userland symbols we export from the vDSO.
 */
VERSION {
   
	LINUX_2.6 {
   
	global:
		clock_gettime;
		__vdso_clock_gettime;
		gettimeofday;
		__vdso_gettimeofday;
		getcpu;
		__vdso_getcpu;
		time;
		__vdso_time;
		clock_getres;
		__vdso_clock_getres;
		__vdso_sgx_enter_enclave;
	local: *;
	};
}

即:

__vdso_clock_gettime
__vdso_gettimeofday
__vdso_getcpu
__vdso_time
__vdso_clock_getres
__vdso_sgx_enter_enclave

通常,vDSO遵循将所有符号以“_vdso”或“_kernel”作为前缀的命名约定,以便将它们与其他标准符号区分开。例如,“gettimeofday”函数的名称是“__vdso_gettimeofday”。

可以看到用户态vdso虚拟地址内容一样。

2.3 内核源码解析

内核具体源码解析请参考:
https://tinylab.org/riscv-syscall-part4-vdso-implementation/
https://www.bookstack.cn/read/linux-insides-zh/SysCall-linux-syscall-3.md
https://zhuanlan.zhihu.com/p/611286101

参考资料

Linux 5.13
https://man7.org/linux/man-pages/man7/vdso.7.html
https://blog.rustforever.top/2022/02/10/linux/syscall/vdso/
https://zhuanlan.zhihu.com/p/620578643
https://cloud.tencent.com/developer/article/1517837

相关推荐

  1. CAS<span style='color:red;'>机制</span>

    CAS机制

    2023-12-14 08:36:02      64 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-14 08:36:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-14 08:36:02       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-14 08:36:02       82 阅读
  4. Python语言-面向对象

    2023-12-14 08:36:02       91 阅读

热门阅读

  1. 解决前端跨域问题,后端解决方法

    2023-12-14 08:36:02       67 阅读
  2. Vue2面试题:说一下组件通信有哪些方式?

    2023-12-14 08:36:02       56 阅读
  3. 机器学习---数字聚类案例

    2023-12-14 08:36:02       67 阅读
  4. 如何在PHP中使用PDO(PHP Data Objects)?

    2023-12-14 08:36:02       71 阅读
  5. three.js纹理贴图不清晰,颜色失真

    2023-12-14 08:36:02       74 阅读
  6. OPA—云原生策略模型

    2023-12-14 08:36:02       63 阅读
  7. Python OpenCV将32位图像改为8位图像

    2023-12-14 08:36:02       52 阅读
  8. uniapp 使用wgt包实现热更新具体实现代码

    2023-12-14 08:36:02       56 阅读