系统创建或扩充进程映像时,系统会以逻辑方式将文件的段复制到虚拟内存段。系统以物理方式读取文件的时间和可能性取决于程序的执行行为、系统负载等。
除非进程在执行过程中引用了逻辑页,否则进程不需要物理页。进程通常会保留许多页面不对其进行引用。因此,延迟物理读取可以提高系统性能。要实际达到这种效率,可执行文件和共享库文件必须具有文件偏移和虚拟地址同余(以页面大小为模数)的段映像。
32 位段的虚拟地址和文件偏移对模数 64 K (0x10000) 同余。64 位段的虚拟地址和文件偏移对模数 1 MB (0x100000) 同余。通过将各段与最大页面大小对齐,无论物理页大小如何,文件都适合进行换页。
缺省情况下,64 位 SPARC 程序与 0x100000000 的起始地址链接。整个程序位于 4 GB 上的地址空间内,包括其文本、数据、堆、栈和共享库依赖性。这有助于确保 64 位程序正确,因为如果程序截断其任何指针,则程序在其最低有效的 4 GB 地址空间中将出现错误。尽管 64 位程序在 4 GB 上的地址空间内进行链接,但仍可以使用 mapfile 和链接编辑器的 -M 选项,链接 4 GB 以下的地址空间内的程序。 请参见 /usr/lib/ld/sparcv9/map.below4G。
下图显示了 SPARC 版本的可执行文件。
下表定义了上图中可装入段的各元素。
表 7–28 SPARC: ELF 程序头段(64 K 对齐)
成员 |
文本 |
数据 |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x10000 |
0x24000 |
p_paddr |
未指定 |
未指定 |
p_filesize |
0x3a82 |
0x4f5 |
p_memsz |
0x3a82 |
0x10a4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
下图显示了 x86 版本的可执行文件。
下表定义了上图中可装入段的各元素。
表 7–29 32 位 x86: ELF 程序头段(64 K 对齐)
成员 |
文本 |
数据 |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x8050000 |
0x8064000 |
p_paddr |
未指定 |
未指定 |
p_filesize |
0x32fd |
0x3a0 |
p_memsz |
0x32fd |
0xdc4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
此示例的文件偏移和虚拟地址以文本和数据的最大页面大小为模数同余。根据页面大小和文件系统块大小,最多可有四个文件页包含混合文本或数据。
第一个文本页包含 ELF 头、程序头表和其他信息。
最后一个文本页包含数据起始部分的副本。
第一个数据页包含文本结尾的副本。
最后一个数据页可以包含与运行的进程无关的文件信息。从逻辑上而言,系统会强制执行内存权限,如同每个段是完整而独立的一样。为确保地址空间中的每个逻辑页都具有单独一组权限,各段的地址会进行调整。在前面的示例中,包含文本结尾和数据起始部分的文件区域映射了两次:一次映射到文本的虚拟地址,另一次映射到数据的与之不同的虚拟地址。
前面的示例反映了对其文本段取整的典型的 Solaris 系统二进制文件。
数据段结尾要求对未初始化的数据进行特殊处理,系统将其定义为从零值开始。如果文件的最后一个数据页包含不属于逻辑内存页的信息,则必须将无关数据设置为零,而不是设置为可执行文件的未知内容。
其他三个页面中的混合内容在逻辑上不属于进程映像。没有指定系统是否会清除这些混合内容。以下各图中显示了此程序的内存映像,假定页面大小为 4 KB (0x1000)。为简单起见,这些图仅对一种页面大小进行说明。
可执行文件和共享库在段装入的某个方面有所不同。可执行文件段通常包含绝对代码。为使进程正确执行,段必须位于用于创建可执行文件的虚拟地址处。系统会使用未更改的 p_vaddr 值作为虚拟地址。
另一方面,共享库段通常包含与位置无关的代码。使用此代码,段的虚拟地址在不同进程之间会进行更改,而不会使执行行为无效。
尽管系统会为各个进程选择虚拟地址,但仍会保持各段之间的相对位置。由于与位置无关的代码在各段之间使用相对地址,因此内存中虚拟地址之间的差值必须与文件中虚拟地址之间的差值匹配。
以下各表显示针对多个进程可能指定的共享库虚拟地址,从而说明了固定的相对位置。此外,这些表中还包括基本地址计算。
表 7–30 32 位 SPARC: ELF 共享库段地址示例
源 |
文本 |
数据 |
基本地址 |
---|---|---|---|
文件 |
0x0 |
0x4000 |
0x0 |
进程 1 |
0xc0000000 |
0xc0024000 |
0xc0000000 |
进程 2 |
0xc0010000 |
0xc0034000 |
0xc0010000 |
进程 3 |
0xd0020000 |
0xd0024000 |
0xd0020000 |
进程 4 |
0xd0030000 |
0xd0034000 |
0xd0030000 |
表 7–31 32 位 x86: ELF 共享库段地址示例
源 |
文本 |
数据 |
基本地址 |
---|---|---|---|
文件 |
0x0 |
0x4000 |
0x0 |
进程 1 |
0x8000000 |
0x8004000 |
0x80000000 |
进程 2 |
0x80081000 |
0x80085000 |
0x80081000 |
进程 3 |
0x900c0000 |
0x900c4000 |
0x900c0000 |
进程 4 |
0x900c6000 |
0x900ca000 |
0x900c6000 |
启动动态链接的动态可执行文件或共享库可以包含一个 PT_INTERP 程序头元素。在 exec(2) 过程中,系统将从 PT_INTERP 段检索路径名,并通过解释程序文件段创建初始进程映像。解释程序负责从系统接收控制并为应用程序提供环境。
在 Solaris OS 中,解释程序称为运行时链接程序,即 ld.so.1(1)。