深入理解计算机系统(三):链接

深入理解计算机系统(三):链接


​ ​ ​ ​ ​ ​ ​ ​ 链接是讲各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码的时候,也可以执行于加载时,即程序被加载器加载到内存并执行时,甚至执行于运行时,也就是让应用程序来执行。现代系统里,链接是链接器自动执行的。

​ ​ ​ ​ ​ ​ ​ ​ 链接器有一个好处,我们不需要把一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,并重新链接应用,不需要重新编译其他文件。

静态链接

​ ​ ​ ​ ​ ​ ​ ​ ​ 静态链接器以一组可重定位目标文件和命令行参数作为输入。生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成,每一节都是一个连续的字节序列。指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量又在另外一节中。

​ ​ ​ ​ ​ ​ ​ ​ ​ 链接器主要有2个任务:

  • 符号解析:目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或者一个静态变量。符号解析的目的是将每个符号引用正好和一个符号定义关联起来
  • 重定位:编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来。从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

目标文件

​ ​ ​ ​ ​ ​ ​ ​ ​ 目标文件有3种形式:

  • 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
  • 可执行目标文件。包含二进制代码和数据,其形式可以被直接复制到内存并执行。
  • 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。

​ ​ ​ ​ ​ ​ ​ ​ ​ 编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件。从技术上说,一个目标模块就是一个字节序列,而一个目标文件就是一个以文件形式存放在磁盘中的目标模块。

重定位

​ ​ ​ ​ ​ ​ ​ ​ ​ 重定位会合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:

  • 重定位节和符号定义:在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来子所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

    .data是ELF可重定位目标文件中的一个节,.data指已经初始化的全局和静态C变量,局部C变量在运行时被保存在栈中,既不出现在.data节中,也不会出现在.bss(未初始化的全局和静态C变量)中。

  • 重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。在这一步中,链接器依赖于可重定位目标模块中成为重定位条目的数据结构。

重定位条目:汇编器遇到对最终位置未知的目标引用,会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

加载可执行目标文件

​ ​ ​ ​ ​ ​ ​ ​ ​ 要运行可执行目标文件prog,我们可以在Linux shell的命令行中输入它的名字:

./prog

​ ​ ​ ​ ​ ​ ​ ​ ​ shell会认为prog是一个可执行目标文件,通过调用某个驻留在存储器中称为加载器的操作系统代码来运行它。任何Linux程序都可以通过调用execve函数来调用加载器。加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个程序复制到内存并运行的过程叫做加载。

​ ​ ​ ​ ​ ​ ​ ​ ​ 每个Linux程序都有一个运行时内存映像,在Linux x86-64系统中,代码段总是从地址0x400000处开始,后面是数据段,运行时堆在数据段之后,通过调用malloc库网上增长。堆后面的区域是为共享模块保留的,用户栈总是从最大的合法用户地址开始,向较小内存地址增长。栈上的区域,从地址2的48次方开始,是为内核中的代码和数据保留的,所谓内核就是操作系统驻留在内存的部分。

动态链接共享库

​ ​ ​ ​ ​ ​ ​ ​ ​ 迄今为止,我们都是假设链接器读取一组可重定位目标文件,并把它们链接起来,形成一个输出的可执行文件。实际上,所有的编译系统都提供一种机制,将所有相关的目标模块打包成一个单独的文件,称为静态库。

​ ​ ​ ​ ​ ​ ​ ​ ​ 静态库解决了许多关于如何让大量相关函数对应用程序可用的问题,不过静态库需要定期维护和更新,如果我们想使用最新版本,那就必须了解该库的更新情况,然后显式的将他们的程序和更新了的库重新连接。共享库是解决静态库缺陷的一个创新产物。

​ ​ ​ ​ ​ ​ ​ ​ ​ 共享库是一个目标模块,在运行或者加载的时候,可以加载到任意的内存地址,并合一个在内存中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。共享库也成为共享目标,在Linux系统中通常用.so后缀来表示。微软的操作系统大量使用了共享库,它们就是DDL(动态链接库)。

​ ​ ​ ​ ​ ​ ​ ​ ​ 共享库是以两种不同的方式共享的。首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用它们的可执行的文件中。其次,在内存中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。

.text是ELF可重定位目标文件中的一个节,.text是指已编译程序的机器代码。

​ ​ ​ ​ ​ ​ ​ ​ ​ 被编译为位置无关代码的共享库可以加载到任何地方,也可以在运行时被多个进程共享。为了加载、链接和访问共享库的函数和数据,应用程序也可以在运行时使用动态链接器。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值