Section与Segment

Section与Segment

Section与Segment #

下图从两个视角展示了应用程序的分布,左边是程序在磁盘中的文件布局结构,右边是程序加载到内存中的内存布局结构。

磁盘程序的每一个单元结构称为 Section。可以通过 readelf -S 来查看。内存镜像的每一个单元结构称为 Segment。可以通过 readelf -l 来查看。

多个 Section 往往会对应一个 Segment,例如.text、.rodata 等一些只读的 Section,会被映射到内存的一个只读 / 执行的 Segment 里;而.data、.bss 等一些可读写的 Section,则会被映射到内存的一个具有读写权限的 Segment 里。对于磁盘二进制中一些辅助信息的Section,例如.symtab、.strtab 等,不需要在内存中进行映射。

程序员的自我修养 #

当我们站在操作系统装载可执行文件的角度看问题时,可以发现它实际上并不关心可执行文件各个段所包含的实际内容,操作系统只关心一些跟装载相关的问题,最主要的是段的权限(可读、可写、可执行)。ELF文件中,段的权限往往只有为数不多的几种组合,基本上是三种:

  1. 以代码段为代表的权限为可读可执行的段。
  2. 以数据段和BSS段为代表的权限为可读可写的段。
  3. 以只读数据段为代表的权限为只读的段。

那么我们可以找到一个很简单的方案就是:对于相同权限的段,把它们合并到一起当作一个段进行映射。比如有两个段分别叫“.text”和“.init”,它们包含的分别是程序的可执行代码和初始化代码,并且它们的权限相同,都是可读并且可执行的。假设.text为4 097字节,.init为512字节,这两个段分别映射的话就要占用三个页面,但是,如果将它们合并成一起映射的话只须占用两个页面,

ELF可执行文件引入了一个概念叫做“Segment”,一个“Segment”包含一个或多个属性类似的“Section”。如果将“.text”段和“.init”段合并在一起看作是一个“Segment”,那么装载的时候就可以将它们看作一个整体一起映射,也就是说映射以后在进程虚存空间中只有一个相对应的VMA,而不是两个,这样做的好处是可以很明显地减少页面内部碎片,从而节省了内存空间。

很明显,从链接的角度看,ELF文件是按“Section”存储的;从装载的角度看, ELF文件又可以按照“Segment”划分。

“Segment”的概念实际上是从装载的角度重新划分了ELF的各个段。在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在同一空间。比如可读可执行的段都放在一起,这种段的典型是代码段;可读可写的段都放在一起,这种段的典型是数据段。在ELF中把这些属性相似的、又连在一起的段叫做一个“Segment”,而系统正是按照“Segment”而不是“Section”来映射可执行文件的。

From #

03 | 内存布局:应用程序是如何安排数据的?