进程映像与链接、加载场景图示

将程序装入内存形成进程映像如上图所示。

上图是应用程序通过链接、加载形成内存中进程的典型模式。应用程序由编译形成的若干模块(更通俗的讲,就是 .o 扩展名的可重定位目标程序)和静态库组成,通过链接器以解析模块间任何访问和对库例程的访问。

加载

加载器将被加载模块放置在内存从 x 开始的位置,一般有 3 中方法:

绝对加载

要求给定加载模块总被加载到内存中同一位置

缺点是:

  • 程序员必须知道内存中放置模块时预定的分配策略;
  • 程序的模块体中进行了任何设计插入或删除的修改,所有其他地址都要相应修改;

解决办法是:使用符号表示对程序中的内存访问,在编译或汇编时解析这些符号的引用。

  • a 图中的符号 X、Y 就在编译中,被转化为 b 图中的绝对地址 1024、2224;

可重定位加载

为了支持多道程序共享内存,事先确定加载位置不合时宜,应该在加载时确定——汇编器、编译器不再产生实际的内存绝对地址,而是使用相对于某些已知点的地址(常用程序起始点),如图c

为了正确加载,被加载模块必须包含告知加载器必要的信息——地址访问位置、如何解释位置、长度是多少。这些信息称为重定位地址库,由编译器或汇编器准备。

动态运行时加载

虚存系统中为了提高内存利用率,需要把进程换入换出内存,若在加载时内存访问就被绑定到绝对地址(即使是 c 中也无法实现这一目标,因为它是相对于加载时的地址入口计算的,一经加载就变成通过相对地址访问的绝对地址,不可再变了),则无法实现这一目标。

解决方案是在真正使用到某个绝对地址时再计算。通过将所有内存访问都以相对形式表示,如图 d,不论如何换入换出,所有地址的计算都基于处于内存时的起始地址 x。

这种地址计算通过基址寄存器和界限寄存器等处理器硬件实现。

链接

链接器的功能是把一组目标模块作为输入,产生一个包含完整程序和数据模块的加载模块,并传递给加载器使用。

每个目标模块都可能含有其它模块的地址访问,因此这些访问在单独模块中以符号表示。链接器通过整合所有模块,得到一个完整地加载模块: a 中 A 对 B 的过程调用,在链接起来的 b 中,符号引用变为了对 B 的入口点位置的确切引用。

链接编辑程序

地址链接的性质取决于链接发生时创建的加载模块的类型:

Linkage TimeFunction
Programming time不允许外部程序或数据访问,程序员要将所有引用到的子程序的源代码放入程序中
Compile or assembly time汇编器必须取到每个引用的子程序的源代码,并将其作为一个部件来进行汇编
Load module creation所有目标模块都使用相对地址汇编,这些模块被链接成加载模块,所有访问都相对于最后加载的模块的地址重新声明
Load time直到加载模块被加载到内存时才解析外部访问,此时被访问的动态链接模块附加到加载模块后,整个软件包被加载到内存或虚存
Run time直到处理器执行外部调用时才解析外部访问,此时该进程被中断,需要的模块被链接到调用程序中
通常以如下方式链接得到一个可重定位的加载模块:
  • 每个编译或汇编后的对象模块在创建时都有相对于对象模块起点的引用。
  • 所有这些模块被组合成一个可重定位的加载模块,所有引用都相对于加载模块的原点。
  • 该模块可用作可重定位的加载或动态运行时加载的输入。

(就是上图 ab 的过程)

动态链接器

dynamic linking 指把某些外部模块的链接推迟到创建加载模块之后。因此加载模块中包含到其他程序的未解析的引用,这些引用在加载时或运行时进行解析。

加载时的动态链接

加载时的动态链接load-time dynamic linking有以下步骤:

  • 待加载的加载模块读入内存;
  • 加载模块中到一个外部模块的任何引用,都将使用加载程序查找目标模块并加载之;
  • 将这些引用修改为相对于应用 (刚才第一个被加载的)模块开始处的相对地址。

优点:

  1. 非常方便地并入已改变或已升级的目标模块。相对于静态链接,这类支持模块 (即外部模块)的变化需要重新链接所有应用程序模块。
  2. 动态链接文件中的目标代码方便于代码共享,多个应用程序可以加载同一目标代码或其少量的副本,节约了大量存储空间;
  3. 更容易扩展 OS 的功能。

运行时的动态链接

运行时的动态链接run-time dynamic linking 会将链接工作推迟到执行时。对目标模块的外部引用保留在被加载程序中,调用模块不存在时 OS 定位该模块并加载、链接。在 windows 中这些外部模块称为 DLL(dynamic linking library) 优点是:若一个进程已使用动态链接共享模块,则该模块就已位于内存中,新的进程若需要调用该模块,直接链接即可使用。

DLL 地狱:两个或多个进程共享一个 DLL 模块,但又需要链接不同的版本,则会导致引入不同版本的 DLL 文件。

动态加载允许一个完整地模块到处移动。在某些事务中不能在执行前确定需要哪些目标模块,而是根据事务的性质临时决定并加载、链接到主程序,这时非常适合动态链接——程序单元引用之前,不需要分配内存空间,因而对于分段系统的支持很好。