6.4 进程虚拟空间分布
6.4.1 ELF文件的链接视图和执行视图
可执行文件被映射时,是以系统页的长度为单位的,每个段在被映射时的长度应该为系统页长度的
整数倍。如果不是,那么多余部分也将占用一个页。一个ELF可执行文件往往有十几个段,那么内存
空间的浪费是可想而知的,有没有办法来减少这种浪费?
一个简单的方案就是:对于有相同权限的段,把它们合到一起当作一个段进行映射。比如有两个段分别
叫做“.text”和“.init”,它们包含的分别是程序的可执行代码和初始化代码,权限相同,都是可读并且
可执行。假设.text段为4097字节,.init为513字节,这两个段分别映射的话就要占用三个页面,但是,
如果将他们合并成一起映射的话,只需要两个页面,这里页面的大小为4096字节。
ELF可执行文件引入了一个概念叫做“segment”,一个“segment”包含一个或者多个属性类似的"section"。
正如上面的例子,如果将“.text”和“.init”一起映射,也就是说映射以后在进程的虚拟空间中只有一个
相对应的VMA,而不是两个,这样做的好处是可以很明显的减少页内部碎片,节省内存空间。
自我问题:这里VMA和页的关系?
“segment”的概念实际上是从装载的角度重新划分了ELF的各个段,在将目标文件链接成可执行文件的时候,
链接器会尽量把相同属性权限的段分配在同一空间。在ELF中把这些属性相似的,又连在一起的段叫做一个
“segment”,而系统这是按照“segment”而不是“section”来映射可执行文件的。
下面是一个在C语言教科书中会出现的Hello World程序:
//hello.c
#include <stdio.h>
int main(int argc, char* argv[]) {
printf("Hello World!\n");
return 0;
}
使用readelf -S hello可知,该可执行文件中共有31个段(section)。也可是使用readelf -l hello来查看
ELF的“segment”。正如描述“section”属性的结构叫做段表,描述“segment”的结构叫做程序头(Program Header),
“segment”描述了可执行文件该如何被操作系统映射到进程的虚拟空间,下面是readelf -l hello的输出:
***图6.4.1***
从图中我们可以看出,可执行文件一共有6个segment,从装载的角度看,我们只关心两个“load”类型的
segment,因为只有该类行的segment才是需要被映射的,其他的segment都是在装载时起辅助作用的。
图中下半部分描述了section到segment的映射,把具有相同权限属性的section合并到同一个segment中。
所以总的来说,segment和section是从不同角度来划分同一个ELF可执行文件,这个在ELF中被称为不同的
视图(View),从secetion角度来看就是链接视图(Linking View),从segment角度来看就是执行视图(Execution View)。
ELF可执行文件中有一个专门的数据结构叫做程序头表(Program Header Table),用来保存segment的信息。
因为ELF目标(.o文件)不需要被装载,因此没有程序头表;而ELF可执行文件和共享库文件都有。跟段表一样,
程序头表也是一个结构体数组,描述如下:
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Word p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
}Elf32_Phdr;
p_type: segment的类型,基本上我们只关注“LOAD”类型的segment。
p_offset: segment在文件中的偏移。
p_vaddr: segment的第一个字节在进程虚拟地址空间的起始位置,整个程序头表中,所有LOAD类型元素按照p_vaddr
从小到大排列。
p_paddr: segment的物理装载地址。
p_filesz: segment在ELF文件中所占空间的长度。
p_flags: segment的权限属性。比如可读“R”,可写“W”, 可执行“X”。
p_align: segment的对齐属性,实际对齐字节等于2的p_align次方,比如p_align等于10,那么实际的对齐属性就是
2的10次方,即1024字节。
对于LOAD类型的段来说,p_memsz的值不能小于p_filesz的值,否则就是不合常理,但是,如果p_memsz的值大于
p_filesz的值呢?就表示该segment在内存中所分配的空间大小超过文件中实际大小,这部分多余的空间填充为0.
座位BSS section部分。数据段和BSS段的区别:数据段在程序中初始化,而BSS段的内容全部初始化为0.