[翻译] 简谈编译、汇编、链接和加载 – part VI

Tanslated from: http://www.tenouk.com/ModuleW.html

W.10  运行时链接器和共享库的加载

使用共享库的程序启动时或这一个程序请求动态加载一个共享对象时会触发运行时链接器。因此符号的解析可以在下面两个过程中的任一个完成。

1. 加载时动态链接。程序被从磁盘上加载到内存中,其中未解析的引用被列出,加载器找到所有需要的外部符号,把所有对这些符号的引用更改为相对于程序基地址的内存地址。
2. 运行时动态链接。程序被从磁盘上加载到内存中,其中未解析的引用被原样保留。第一次对这些符号的访问是无效的,从而引发一个software trap。运行时动态链接器侦测到此次trap的原因并找到所需的外部符号。然后这个被访问的符号被加载到内存并被链接到程序中。

运行时链接器包含在C的运行时库中,它加载共享库(.so文件)时会完成几个任务。动态section提供信息给链接器,告诉它这个库文件链接的其它库文件。它同时提供需要哪些重定位(reloaction)操作以及需要解析的外部符号。运行时链接器会首先加载需要的其它库文件。然后为每个库文件执行重定位操作。有些重定位仅限于库文件本身,而有些则需要运行时链接器解析一个全局符号。对于后一种情况,运行时链接器会搜索一个库文件列表来寻找这个符号。在ELF文件中,会使用hash table来进行符号的查找,所以这个过程是很快的。一旦所有的重定位操作执行完毕,就开始调用在共享库文件的init部分中注册的初始化函数。一些C++的实现使用这个特性来调用全局contructor。

 

W.11  符号名称解析
当运行时链接器加载了共享库文件,还需要解析库文件中的符号。这里,符号解析的顺序和范围是很重要的。如果一个共享库调用的函数碰巧在几个库文件中都存在,那么搜索这个函数的顺序就至关重要了。这也是为什么操作系统为库文件加载定义了几个选项。所有具有全局范围的对象(可执行文件和库文件)会被保存到一个列表中(全局列表)。默认,任何全局对象的所有符号对加载进来的共享库都是可用的。全局列表开始时包含可执行文件以及程序启动时加载的库文件。

 

W.12 动态地址翻译
以内存管理的角度看,现代的具有多任务的操作系统通常会实现动态重定位,而非静态。所有程序在地址空间中的布局几乎是相同的。这个动态重定位(以处理器的角度,称为动态地址翻译)产生一下的假象:

1. 每个进程都可以使用以0开始的内存地址,即使其它进程已经在运行了,或者同一个程序被启动多次
2. 地址空间是受保护的
3. 甚至可以让进程认为它拥有比实际物理内存大得多的内存可用(虚拟内存)

动态重定位中,每次引用地址都会动态变化。虚拟内存地址(或者称为逻辑地址)由某个进程产生,而物理地址则是运行时在物理内存中的实际地址。地址的翻译通常由处理器内嵌的内存管理单元(MMU)完成。
虚拟地址是相对于进程而言的。每个进程都相信它的虚拟地址从0开始。进程完全不知道这个地址在物理内存中的实际地址。代码完全以虚拟地址的方式运行。
如果一个虚拟地址超出了进程的(实际)地址范围,MMU可以拒绝翻译此地址,如产生一个segmentation fault。这为所有进程提供了保护。
翻译过程中,我们还可以把进程地地址空间中的一部分在内存和磁盘间移动(称为swapping或paging),这就是为什么进程可以使用比实际物理内存还大的地址空间。

动态重定位可以以下图来表示: