Linux时间子系统2: clock_gettime的VDSO机制分析

2024-06-13 05:12

本文主要是介绍Linux时间子系统2: clock_gettime的VDSO机制分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        在之前分析clock_gettime的文章中接触到了VDSO,本篇文章是对VDSO的学习总结,借鉴了很多前人的经验。

   1. 什么是VDSO

        vDSO:virtual DSO(Dynamic Shared Object),虚拟动态共享库,内核向用户态提供了一个虚拟的动态共享库。在 Linux 众多的系统调用中,有一部分存在以下特点:

  • 系统调用本身很快,主要时间花费在 trap 过程
  • 无需高特权级别权限

        这部分系统调用如果能够直接在用户空间中执行,则能够对性能有较大的改善。gettimeofday 就是一个典型的例子,它仅仅只是读取内核中的时间信息,而且对于许多应用程序来说,读取系统时间是必要的同时也是频率很高的行为。

        例如在ARM64平台到处的接口如下:

   aarch64 functionsThe table below lists the symbols exported by the vDSO.symbol                   version──────────────────────────────────────__kernel_rt_sigreturn    LINUX_2.6.39__kernel_gettimeofday    LINUX_2.6.39__kernel_clock_gettime   LINUX_2.6.39__kernel_clock_getres    LINUX_2.6.39

vdso在不同平台的命名略有不同, 如下:

user ABI   vDSO name
─────────────────────────────
aarch64    linux-vdso.so.1
arm        linux-vdso.so.1
ia64       linux-gate.so.1
mips       linux-vdso.so.1
ppc/32     linux-vdso32.so.1
ppc/64     linux-vdso64.so.1
riscv      linux-vdso.so.1
s390       linux-vdso32.so.1
s390x      linux-vdso64.so.1
sh         linux-gate.so.1
i386       linux-gate.so.1
x86-64     linux-vdso.so.1
x86/x32    linux-vdso.so.1

         因为vdso本身是内核提供的机制,被编译进内核,所以并没有具体的文件路径,以上名称是C库访问时需要用到。

        vdso和vsyscall的对比以及vdso引入linux kernel的时间可以参考

The VDSO on arm64

2. 使用VDSO

使用VDSO的方式有三种

  • 使用 C 标准库
  • 使用 dlopen 获取函数地址
  • 使用 getauxvel 获取函数地址

具体可以参考这篇文章:articles/20220717-riscv-syscall-part3-vdso-overview.md · 泰晓科技/RISCV-Linux - Gitee.com

3. VDSO实现原理

a. vdso的编译以及如何集成到内核

        可直接参考链接:泰晓科技 / RISCV-Linux

        这里附上文章中的图片:

b. vdso的几个问题

vdso的初始化同样在上面的文章中讲得很详细了,我们按照如下思路再捋一遍。

1) vdso.so不是给内核用的,但是被内核包含,用户态如何调用到vdso中的代码呢?

2) 内核如何更新数据,数据放在哪里让用户态可以获取到呢

3)用户态通过vdso.so中的代码如何访问到内核中的数据呢?

c. vdso中的代码如何共享给用户态

        vdso被包含进内核,而不是链接进内核,这是因为vdso.so中的代码段是给用户态进程使用的,那么很显然用户态进程需要映射代码段的地址到进程的地址空间。

       首先,在vdso.S(/arch/arm64/kernel/vdso)中,vdso_start,vdso_end定义了vdso代码段的起始地址和结束地址

	.globl vdso_start, vdso_end.section .rodata.balign PAGE_SIZE
vdso_start:.incbin "arch/arm64/kernel/vdso/vdso.so".balign PAGE_SIZE
vdso_end:.previous

 vDSO 内核中代码部分地址初始化的时候,vdso_code_start和 vdso_code_end分别被赋值了 vdso_start和 vdso_end,在__vdso_init函数中,使用vdso_info[abi].cm->pages记录了代码段的物理页信息,如下:

	/* Grab the vDSO code pages. */pfn = sym_to_pfn(vdso_info[abi].vdso_code_start);for (i = 0; i < vdso_info[abi].vdso_pages; i++)vdso_pagelist[i] = pfn_to_page(pfn + i);vdso_info[abi].cm->pages = vdso_pagelist;

有了物理页信息,那么用户态进程访问代码段,只需要建立物理页与进程虚拟地址空间的映射即可,用户态进程execve解析elf文件时,在内核会调用arch_setup_additional_pages,__setup_additional_pages则会从vdso_info中取出代码段和数据段的page进行映射,从而用户进程就可以访问代码段和数据段的数据了。

	ret = _install_special_mapping(mm, vdso_base, VVAR_NR_PAGES * PAGE_SIZE,VM_READ|VM_MAYREAD|VM_PFNMAP,vdso_info[abi].dm);if (IS_ERR(ret))goto up_fail;if (IS_ENABLED(CONFIG_ARM64_BTI_KERNEL) && system_supports_bti())gp_flags = VM_ARM64_BTI;vdso_base += VVAR_NR_PAGES * PAGE_SIZE;mm->context.vdso = (void *)vdso_base;ret = _install_special_mapping(mm, vdso_base, vdso_text_len,VM_READ|VM_EXEC|gp_flags|VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,vdso_info[abi].cm);

用户态映射后的示意图:

图片来自:杂谈:vdso原理 - 知乎

d. 内核如何更新vdso数据,以及用户态如何访问

有了上面访问代码段的机制,用户态访问数据的机制自然不用再说了,需要注意的是dm 的初始化在 vvar_fault 函数中实现。vvar_fault 是 dm 缺页中断的回调函数。那么内核态如何更新vsdo数据呢,主要通过update_vsyscall更新vdso_data变量

用户态调用vdso函数,以 gettimeofday为例分析 vDSO 函数调用流程,libc 调用 vsdo.so 中 __kernel_gettimeofday 函数, __kernel_gettimeofday 访问 vvar 数据。除了第一次访问会触发 Page Fault (实测开销大于syscall),整个过程不会陷入内核态。

gettimeofday->__kernel_gettimeofday=> special_mapping_fault
__kernel_gettimeofday->__arch_get_vdso_data=> special_mapping_fault->vvar_fault
    __arch_get_hw_counter //从硬件 timer 读取 cntvct_el0 寄存器得到距离上次更新vdso_data的时间差,加上 vdso_data 里的时间得到最终时间

参考资料:

The vDSO on arm64

泰晓科技 / RISCV-Linux

杂谈:vdso原理 - 知乎

        

这篇关于Linux时间子系统2: clock_gettime的VDSO机制分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1056372

相关文章

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序