VDSO

news/2025/3/19 13:38:14/

VDSO(Virtual Dynamically-lined Shared Object),这是一个由内核提供的虚拟.so文件,它不在磁盘上,而在内核里,内核将其映射到一个地址空间中,被所有程序共享,正文段大小为一个页面。

$ ldd /bin/lsmod
linux-vdso.so.1 =>  (0x00007ffff7fdf000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c42000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fe0000)

这里的linux-vdso.so.1就是vdso文件,现在的内核映射提供了进程随机地址化功能,vdso文件以及其他.so文件的映射地址会有不同的offset,通过

$ echo "0" > /proc/sys/kernel/randomize_va_space (root环境) 或

$ sudo sysctl -w kernel.randomize_va_space=0

可以关闭随机化地址功能,接下来把vdso的内容从/proc/self/mem中dd出来反汇编研究一下

$ cat /proc/self/maps
00400000-0040c000 r-xp 00000000 08:01 1430947                            /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 1430947                            /bin/cat
0060d000-0062e000 rw-p 00000000 00:00 0                                  [heap]
7ffff7a59000-7ffff7bd3000 r-xp 00000000 08:01 801437                     /lib/x86_64-linux-gnu/libc-2.13.so
7ffff7bd3000-7ffff7dd3000 ---p 0017a000 08:01 801437                     /lib/x86_64-linux-gnu/libc-2.13.so
7ffff7dd3000-7ffff7dd7000 r--p 0017a000 08:01 801437                     /lib/x86_64-linux-gnu/libc-2.13.so
7ffff7dd7000-7ffff7dd8000 rw-p 0017e000 08:01 801437                     /lib/x86_64-linux-gnu/libc-2.13.so
7ffff7dd8000-7ffff7ddd000 rw-p 00000000 00:00 0
7ffff7ddd000-7ffff7dfc000 r-xp 00000000 08:01 801438                     /lib/x86_64-linux-gnu/ld-2.13.so
7ffff7e68000-7ffff7fdf000 r--p 00000000 08:01 1832159                    /usr/lib/locale/locale-archive
7ffff7fdf000-7ffff7fe2000 rw-p 00000000 00:00 0
7ffff7ff9000-7ffff7ffb000 rw-p 00000000 00:00 0
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 0001f000 08:01 801438                     /lib/x86_64-linux-gnu/ld-2.13.so
7ffff7ffd000-7ffff7ffe000 rw-p 00020000 08:01 801438                     /lib/x86_64-linux-gnu/ld-2.13.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

 

$ dd if=/proc/self/mem of=vdso.so bs=4096 skip=$[0x7ffff7ffb] count=1
dd: `/proc/self/mem': cannot skip to specified offset
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.00105878 s, 3.9 MB/s
$ file vdso.so
vdso.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped

vdso是将内核态的调用映射到用户态的地址空间中,使得调用开销更小,路径更好。传统的系统调用方式是通过软中断指令int 0x80实现的,在x86保护模式中,处理INT中断指令时,CPU首先从中断描述符表IDT中取出对应的门描述符,判断门描述符的种类,然后检查门描述符的级别DPL和INT指令调用者的级别CPL,当CPL<=DPL时,即INT调用者级别高于描述符指定级别时,才能成功调用,最后再根据描述符的内容,进行压栈、跳转、权限级别提升。内核代码执行完毕之后,调用IRET指令返回,IRET指令恢复用户栈,并跳转回低级别代码。

其实,在发生系统调用,从用户层进入内核层的这个过程浪费了不少CPU周期,例如,系统调用必然需要由Ring3进入Ring0(由内核调用INT指令的方式除外,这多半属于Hacker的内核模块所为),权限提升之前和之后的级别是固定的,CPL肯定是3,而INT 0x80的DPL肯定也是3,这样CPU检查门描述符的DPL和调用者的CPL就是完全没有必要的,正是由此,Intel x86 CPU从PII 300(Family 6, Model 3, Stepping 3)之后,开始支持新的系统调用指令sysenter/sysexit。sysenter指令用于由Ring3进入Ring0(sysenter指令可用于3、2、1特权级),sysexit指令用于由Ring0返回Ring3(sysexit只能在0级特权使用),由于没有特权级别检查的处理,也就没有了压栈操作,所以执行速度比INT n/IRET快了不少。

系统调用多被封装成库函数提供给应用程序使用,应用程序调用库函数后,由glibc库负责进入内核调用系统调用函数,在老版glibc中,库函数就是通过int指令来完成系统调用的,而2.6之前的内核提供的系统调用接口也很简单只要在IDT中提供INT 0x80的入口,库就可以完成中断调用。

在2.6中,内核代码包含了对int 0x80中断方式和sysenter指令方式调用的支持,因此内核会给用户空间提供一段入口代码,内核启动时根据CPU类型,决定这段代码采取哪种系统调用方式,对于glibc来说,无需考虑系统调用方式,直接调用这段入口代码, 即可完成系统调用,这样做还可以尽量减少对glibc的改动,在glibc的源码中,只需将"int $0x80"指令替换成"call入口地址"即可,这个地址便是vsyscall的首地址。

至于在新版内核中,据观察系统调用都是利用syscall()函数实现的

SYSCALL_DEFINE0(getpid)
{ return task_tgid_vnr(current); }
#define SYSCALL_DEFINE0(name)    asmlinkage long sys_##name(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
static inline int sys_perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
attr->size = sizeof(*attr);
return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}

 查看vdso.ld.S文件,可以看到

/* Linker script for 64-bit vDSO. We #include the file to define the layout details. Here we only choose the prelinked virtual address. This file defines the version script giving the user-exported symbols in the DSO. We can define local symbols here called VDSO* to make their values visible using the asm-x86/vdso.h macros from the kernel proper. */
#define VDSO_PRELINK 0xffffffffff700000
#include "vdso-layout.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;
local: *;
};
}
VDSO64_PRELINK = VDSO_PRELINK;
$ cat vdso.dump | grep ">:"
ffffffffff700700 <__vdso_clock_gettime-0x220>:
ffffffffff700920 <__vdso_clock_gettime>:
ffffffffff7009a0 <__vdso_gettimeofday>:
ffffffffff700a30 <__vdso_time>:
ffffffffff700a60 <__vdso_getcpu>:

这样看来,只有在调用clock_gettime、gettimeofday、getcpu、time这些系统调用时,才会使用vdso,其他系统调用是通过syscall实现的,原因是:快速系统调用指令比起中断指令来说,其消耗时间必然会少一些,但是随着CPU设计的发展,将来应该不会再出现类似Intel pentium4这样悬殊的差距,而快速系统调用指令比起中断方式的系统调用,还存在一定局限,例如无法在一个系统调用处理过程中再通过快速系统调用指令调用别的系统调用,因此,并不一定每个系统调用都需要通过快速系统调用指令来实现,比如,对于复杂的系统调用例如fork,两种系统调用方式的时间差和系统调用本身运行消耗的时间来比,可以忽略不计,此处采取快速系统调用指令方式就没有什么必要了,而真正应该使用快速系统调用指令方式的,是那些本身运行时间很短,对时间精确性要求高的系统调用,例如getcpu、gettimeofday等,因此,采取灵活的手段,针对不同的系统调用采取不同的方式,才能得到最优化的性能和实现最完美的功能。

References:

1. Linux 2.6 对新型 CPU 快速系统调用的支持http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html

2. linux下的vdso与vsyscall

3. Linux下的VDSO


http://www.ppmy.cn/news/121399.html

相关文章

生态系统NPP及碳源、碳汇模拟(CASA模型)-教程

详情点击链接&#xff1a;生态系统NPP及碳源、碳汇模拟&#xff08;CASA模型&#xff09; 一&#xff0c;CASA模型 1.1 碳循环模型 1.2 CASA模型 1.3 CASA下载与安装 1.4 CASA注意 二&#xff0c;CASA初步操作 2.1 ENVI界面 2.2 ENVI 数据及格式 2.3 基于ENVI的CASA模…

限量内测名额释放:微信云开发管理工具新功能

我们一直收到大家关于云数据库管理、快速搭建内部工具等诉求&#xff0c;为了给大家提供更好的开发体验&#xff0c;结合大家的诉求&#xff0c;云开发团队现推出新功能「管理工具」&#xff0c;现已启动内测&#xff0c;诚邀各位开发者参与内测体验。 什么是「管理工具」 管…

io之socket编程

写在前面 本文通过socket编程来实现一个简单的HttpServer。 1&#xff1a;单线程版本 我们使用单线程来实现一个HttpServer&#xff0c;如下&#xff1a; package dongshi.daddy.io.httpserver;import java.io.PrintWriter; import java.net.ServerSocket; import java.net.…

C语言获取Linux单网卡的多IP地址

环境配置 上一篇文章主要讲了AIX系统下的单网卡多IP的IP的配置以及C语言获取的方法。相比AIX&#xff0c;Linux下配置就方便得多。 首先找到我们需要配置的网卡名&#xff0c;比如p2p2, 进入到/etc/sysconfig/network-scripts/目录&#xff0c;找到ifcgf-p2p2文件&#xff0c;…

LeetCode 70. 爬楼梯 计算爬楼梯共计多少种方法可达

1、问题&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2.…

第十九章行为型模式—中介者模式

文章目录 中介者模式解决的问题结构实例存在的问题适用场景 中介者模式和代理模式的区别代理模式中介模式桥接模式总结 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务&#xff0c;它涉及算法…

充电桩检测设备厂家TK4860C交流充电桩检定装置

TK4860系列是专门针对现有交流充电桩现场检测过程中接线复杂、负载笨重、现场检测效率低等问题而研制的一系列高效检测仪器&#xff0c;旨在更好的开展充电桩的强制检定工作。 充电桩检测设备是一款在交流充电桩充电过程中实时检测充电电量的标准仪器&#xff0c;仪器以新能源…

小米max2装鸿蒙,小米Max2最全评测 小米Max2值不值得买?

小米Max2最全评测 小米Max2值不值得买&#xff1f;【IT168 评测】2016年的5月10日&#xff0c;小米首款大屏手机小米Max发布。而在一代小米Max发布初期&#xff0c;外界对这款手机的市场预期并不高&#xff0c;毕竟有着6.44寸大屏的它定位有些过于“精准”&#xff0c;面向的用…