• Linux程序执行前发生了什么——程序员的自我修养


    程序的加载和链接是内核完成的吗

    知道Linux有fork和exec系统调用,可以创建新进程和执行可执行的文件,fork就是内核自己数据结构的维护,可以理解,那么exec指定一个文件之后,怎么加载文件和动态链接?

    Interpreter

    • 内核在执行ELF文件的时候,把执行文件映射到用户空间内存
    • 在ELF中寻找特殊子段INTERP
    • kernel把interpreter映射到用户空间,控制权移交到interpreter
    • interpreter完成load、link,执行用户程序

    在ELF可执行文件中会指定ELF interpreter,Linux上的可能是/lib64/ld-linux-x86-64.so.2,一个库的全路径,内核把控制权交给Interpreter这个可执行的库。这不是内核的部分,通常是glibc提供。

    所以,有需要强调一遍Linux只是内核,而用户空间的事情是操作系统,从glibc这一层开始。理论上内核只是约定了一个Interpreter这样一个入口的概念,向用户空间声明,调用exec syscall之后,加载可执行文件到内存,内存映射,然后控制权交到用户空间,进程从Interpreter开始执行。
    尽管一个OS只有一种interpreter,但是理论上如果不使用glibc提供的interpreter,第三方单独开发一个interpreter也是可以的。interpreter约定应用程序如何编排指令(包括是否支持动态链接等特性)syscall已经提供了mmap为动态链接提供了底层实现的可能性。

    动态链接库

    每个进程的地址空间都涵盖动态链接库,只是动态链接库在内存中真实存在只有一份,通过内核的内存映射达到多个进程地址空间能涵盖的效果。
    通过/proc/<pid>/maps查看映射的文件,这个映射不同于loading,后者意味着需要symbol linking

    Kernel完成ELF文件到内存的mapping后,kernel把dynamic linker也mapping到程序地址空间。内核还mapping其他如bss,把参数和环境压栈。确定ld.so的开始地址后(还是通过读取特定的section),设定进程的开始地址。ELF中特殊的section记录程序的开始位置。

    ld.so检查ELF文件中所有的动态链接库,mapping到内存,但是并不立即link,因为mapping快,而符号链接比较耗时,用时才链接。
    ldd /bin/ls可以查看ELF的动态链接库,注意ldd是一个工具应用程序,而ld.so是一个库,是所谓的interpreter,需要完成link和load。

    动态链接库多个程序共用,为什么数据不会产生干扰?

    库里面有函数,函数有局部变量,而函数只有一份,为什么多个程序调用同一段函数,局部变量不会产生干扰?

    • 局部变量:都是分配在栈上,每个进程的栈是独立的。在函数中声明一个局部变量需要分配空间,这个空间就是栈上的空间,在库函数后续指令对于局部变量的访问都是通过栈基址偏移吧,连个变量名都不会保存到动态库的代码段。在库函数调用之前需要在进程的栈上准备变量“槽位”

    • 全局变量:Windows和Unix-like不同。Windows上不允许使用动态库里的全局变量,链接出错。除非使用特殊语法,本质上是动态库和可执行程序都静态链接了定义了变量的模块:

        #ifdef COMPILING_THE_DLL
        #define MY_DLL_EXPORT extern "C" __declspec(dllexport)
        #else
        #define MY_DLL_EXPORT extern "C" __declspec(dllimport)
        #endif
        MY_DLL_EXPORT int my_global;
      

      Unix-like系统导出所有的extern变量和函数,同一个动态库在多个进程中是不同的空间,即在不同进程中表现为不同地址,但是访问的变量是同一个,变量的定义只有一份。

    最后,动态库应该避免全局变量。C++标准没有定义动态库,动态库是平台相关的领域——前文也提到OS从glibc开始,glibc不仅包括代码和库也包括所涉及的一些概念,interpreter,链接器,连接方式等。

  • 相关阅读:
    mysql查找数据库中是否已经存在某张表
    springboot中generator相关配置文件
    同步网络时间到linux服务器(先修改时区再进行同步网络时间)
    踩坑记:前后端分离的项目启动时间过长
    踩坑记:mysql timeStamp默认值0000-00-00 00:00:00 报错
    一次踩坑记录(使用rpc前后端分离服务总是注册不上)
    Maven项目结合POI实现导入导入导入导入导入Excl表格Demo-亲测可用
    Maven项目结合POI导出Excl表格Demo-亲测可用
    集合系列之fail-fast 与fail-safe 区别
    并发编程JUC系列AQS(CountDownLatch、CyclicBarrier、Semaphore)
  • 原文地址:https://www.cnblogs.com/linlei2099/p/8934421.html
Copyright © 2020-2023  润新知