分页管理与分段管理下的动态链接
1. 动态链接与逻辑地址空间
首先,我们必须理解动态链接做了什么。
-
静态链接: 在编译链接阶段,将程序所用到的库函数代码(如
printf
)完完整整地复制到最终生成的可执行文件中。这导致文件体积大,且多个进程运行时,内存中会有多份相同的库代码。 -
动态链接: 在编译链接阶段,只在可执行文件中保留一个“桩”(stub)或“符号引用”,记录下“我需要用到某个库的某个函数”。真正的链接过程被推迟到程序加载时 (Load-time linking) 或首次运行时 (Run-time linking)。
2. 动态链接在分段存储管理下的实现
分段管理与动态链接的结合非常自然、直观。
-
关系: 在分段系统中,一个进程的逻辑地址空间本身就是由多个逻辑上独立的段构成的集合。比如,主程序是一个代码段、全局变量是一个数据段、栈是一个栈段。这种模块化的结构与程序和库的组织方式完美契合。
-
实现机制:
-
当加载器需要将一个共享库(如
libc.so
)映射到进程 P1 的逻辑地址空间时,它可以非常干脆地为这个库创建一个新的段。 -
加载器将
libc.so
加载到物理内存的某个位置。 -
然后,在进程 P1 的段表中,增加一个新表项。假设分配的段号是
S
,表项内容就是libc.so
所在的物理基地址和它的长度。 -
从此,进程 P1 就可以通过二维的逻辑地址
(段号S, 段内偏移量)
来访问libc.so
中的代码和数据了。 -
当另一个进程 P2 也需要使用
libc.so
时,加载器无需在物理内存中重新加载一份。它只需在 P2 的段表中也增加一个表项(段号可以不同,比如是S'
),但让这个表项指向与 P1 相同的物理基地址。
-
优点:
-
逻辑清晰: “一个共享库就是一个独立的段”,这个模型非常符合程序员对模块化的认知。
-
保护与共享方便: 可以在段表中为共享库段设置独立的保护位(如只读、可执行),实现清晰的权限管理。
3. 动态链接在页式存储管理下的实现
虽然分页管理的逻辑地址空间是一维、线性的,不像分段那样在结构上“模块化”,但它同样能高效地支持动态链接。
-
关系: 在分页系统中,虽然整个逻辑地址空间是线性的,但操作系统依然可以将其中的一段连续的虚拟地址范围分配给共享库使用。
-
实现机制:
-
当加载器需要将
libc.so
映射到进程 P1 的逻辑地址空间时,它首先会在 P1 的一维线性虚拟地址空间中,找到一片尚未使用的、足够大的连续区域(例如,从虚拟地址0xB7000000
到0xB7180000
)。 -
加载器将
libc.so
加载到物理内存中,占据若干个物理页帧(这些页帧在物理上可以不连续)。 -
然后,在进程 P1 的页表中,将步骤1中选定的那段虚拟地址范围所对应的页表项,逐一填充,使其指向步骤2中
libc.so
所在的那些物理页帧。 -
从此,进程 P1 就可以通过一维的逻辑地址(即虚拟地址,如
0xB7001234
)来访问libc.so
了。地址翻译机构会自动通过页表找到正确的物理位置。 -
当另一个进程 P2 也需要
libc.so
时,加载器同样在 P2 的虚拟地址空间中找一片空闲区域,然后修改 P2 的页表,让相应的页表项指向与 P1 相同的那些物理页帧。
-
总结与对比:
特性 | 分段管理下的动态链接 | 分页管理下的动态链接 |
---|---|---|
逻辑视图 | 将共享库视为一个新的、独立的段,加入到进程的段集合中。 | 在进程线性的虚拟地址空间中,找一片连续的空闲区域来映射共享库。 |
映射单位 | 段。整个库作为一个或多个段进行映射。 | 页。库所占的虚拟地址范围被逐页映射到物理页帧。 |
实现核心 | 修改进程的段表 (Segment Table),增加新的段描述符。 | 修改进程的页表 (Page Table),填充虚拟页到物理帧的映射关系。 |
共享方式 | 不同进程的段表项指向同一个物理段基地址。 | 不同进程的页表项指向同一组物理页帧。 |