Linux中可执行文件的装载

在linux中,对于32位机器4G内存来说,内存被划分为两个部分,用户空间和内核空间,高地址的1G空间为内核空间,低地址的3G空间为用户空间,也就是进程可以访问得到的空间。
对于采用了虚拟存储技术的系统来说,可执行文件的装载一般采用的是页映射的方式。页映射将内存和磁盘中的数据和指令按照“页”为单位划分成若干个页,以后所有的装载和操作的单位就是页。
从操作系统的角度来看,一个进程最关键的特征是拥有独立的虚拟地址空间。很多时候一个程序被执行同时伴随着一个新的进程的创建,然后装载相应的可执行文件并且执行。在有虚拟存储的情况下,上述过程最开始需要做三件事:

  1. 创建一个独立的虚拟地址空间(这部分只需要对已每一个进程新建一个页表就可以了)
  2. 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。
  3. 将CPU的指令寄存器设置为可执行文件的入口地址,启动运行。

第一步只是建立了虚拟内存和实际物理地址的映射,第二步所做的是虚拟空间到可执行文件的映射关系。我们知道,当程序发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该缺页从磁盘读取到内存中,在设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行,但是很明显的一点就是,但操作系统捕获到缺页时,它应知道程序当前所需要的页在可执行文件中的哪一个位置。这就是虚拟空间到可执行文件之间的映射关系。从某种角度来看,这一步是整个装载过程中最重要的一点,也是传统意义上“装载”的过程。

ELF文件中的段需要映射到进程虚拟空间中的页里面,段所占用的页数为正整数,一个页面只能被一个段占有,即使该页面没有别用完。由于一个ELF文件会有很多个段,如果按照这个方法的话,那么会在进程虚拟内存里面出现很多个碎片,造成内存浪费。所以ELF可执行文件引入了一个概念叫做”segment”,一个”segment”包含一个或多个属性类似的段。”segment”的概念实际上是从装载的角度重新划分了ELF的各个段,在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在同一个空间。比如可读可执行的段都放在一起,这种段的典型是代码段,可读可写的段放在一起,这种典型的为数据段。在ELF中,把这些属性相似的、又连在一起的段叫做一个”segment”,而系统正是按照”segment”而不是段来映射可执行文件的。

堆的最大申请空间

在Linux内存中,以32位机器4G的配置来说,高地址的1G为内核的空间,用户可用空间为3G,在可执行文件装载到虚拟内存中,需要占用一些空间,比如代码段和数据段以及bbs段,都需要有相应的空间来存储,所以在申请堆空间的时候,最大的可申请空间为除掉这些已经被占有的空间所剩下的用户空间。最大的堆可申请空间为2.9G左右。因为需要除掉程序本身大小、可用到的动态/共享库数量以及大小、程序栈数量、大小等。