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


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

       1. 什么是VDSO

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

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

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

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

    1. aarch64 functions
    2. The table below lists the symbols exported by the vDSO.
    3. symbol version
    4. ──────────────────────────────────────
    5. __kernel_rt_sigreturn LINUX_2.6.39
    6. __kernel_gettimeofday LINUX_2.6.39
    7. __kernel_clock_gettime LINUX_2.6.39
    8. __kernel_clock_getres LINUX_2.6.39

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

    1. user ABI vDSO name
    2. ─────────────────────────────
    3. aarch64 linux-vdso.so.1
    4. arm linux-vdso.so.1
    5. ia64 linux-gate.so.1
    6. mips linux-vdso.so.1
    7. ppc/32 linux-vdso32.so.1
    8. ppc/64 linux-vdso64.so.1
    9. riscv linux-vdso.so.1
    10. s390 linux-vdso32.so.1
    11. s390x linux-vdso64.so.1
    12. sh linux-gate.so.1
    13. i386 linux-gate.so.1
    14. x86-64 linux-vdso.so.1
    15. 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代码段的起始地址和结束地址

    1. .globl vdso_start, vdso_end
    2. .section .rodata
    3. .balign PAGE_SIZE
    4. vdso_start:
    5. .incbin "arch/arm64/kernel/vdso/vdso.so"
    6. .balign PAGE_SIZE
    7. vdso_end:
    8. .previous

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

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

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

    1. ret = _install_special_mapping(mm, vdso_base, VVAR_NR_PAGES * PAGE_SIZE,
    2. VM_READ|VM_MAYREAD|VM_PFNMAP,
    3. vdso_info[abi].dm);
    4. if (IS_ERR(ret))
    5. goto up_fail;
    6. if (IS_ENABLED(CONFIG_ARM64_BTI_KERNEL) && system_supports_bti())
    7. gp_flags = VM_ARM64_BTI;
    8. vdso_base += VVAR_NR_PAGES * PAGE_SIZE;
    9. mm->context.vdso = (void *)vdso_base;
    10. ret = _install_special_mapping(mm, vdso_base, vdso_text_len,
    11. VM_READ|VM_EXEC|gp_flags|
    12. VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
    13. 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原理 - 知乎

            

  • 相关阅读:
    python 空间滤波
    什么是分层架构
    双11便宜云服务器有哪些值得推荐的
    nvm包管理工具下载安装
    [NLP Begin] Classical NLP Methods - HMM
    《面试八股文》之Dubbo17卷
    Istio实践(2)- 流量控制及服务间调用
    C++实现kafka的消费者客户端
    [附源码]java毕业设计石林县石漠化信息查询分析系统
    MySQL最基本的常识
  • 原文地址:https://blog.csdn.net/Bluetangos/article/details/136743193