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

相关文章

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

MySQL中的锁机制详解之全局锁,表级锁,行级锁

《MySQL中的锁机制详解之全局锁,表级锁,行级锁》MySQL锁机制通过全局、表级、行级锁控制并发,保障数据一致性与隔离性,全局锁适用于全库备份,表级锁适合读多写少场景,行级锁(InnoDB)实现高并... 目录一、锁机制基础:从并发问题到锁分类1.1 并发访问的三大问题1.2 锁的核心作用1.3 锁粒度分

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重