5. 可执行文件的装载与进程【《链接、装载与库》学习笔记】

进程虚拟地址空间

每个程序被运行起来以后拥有自己独立的虚拟地址空间,大小由计算机硬件平台决定,具体说是由CPU位数决定。

一般来说,C语言指针大小的位数与虚拟空间的位数相同。

PAE(Physical Address Extension)可以访问更多的物理内存。

操作系统而已提供一个窗口映射的方法,把额外的内存映射到进程地址空间。

装载

动态装入:把程序最常用的部分留在内存中,将一些不太常用的数据存放在磁盘中。覆盖装入(Overlay)和页映射(Paging)是两种典型动态装载方法。

覆盖装载

程序员通过手工方式将模块按照他们的调用依赖关系组成树状结构。

页映射

使用一种”装载管理器“(其实就是操作系统的存储管理器)使用FIFO的方式将程序的不同页装入内存。

因为这样动态装载的机制,每次页装入时都需要进行重定位。

进程的建立

  1. 创建一个独立的虚拟地址空间:分配一个页目录
  2. 读取可执行文件头,并建立虚拟空间与可执行文件的映射关系:装载段进入虚拟内存区域(VMA, Virtual Memory Area)

由于可执行文件在装载时实际上是被映射的虚拟空间,所以可执行文件很多时候被称为映像文件(Image)

  1. 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行。

页错误

但通过CPU发现某页面是空页面,被认为是一个页错误,开始计算VMA并进行装载。随着进程的执行,页作物也会不断产生,操作系统也会为进程分配相应物理页面满足进程执行需求。

进程虚存空间分布

操作系统实际上并不关心而执行文件各个段所包含的实际内容,只关心与装载相关的问题,如权限(可读、可写、可执行)。

为了内存浪费,对于相同权限的段,可以合并到一起当作一个段来映射。ELF的Segment包含一个或多个属性类似的Section

可以使用readelf查看ELF的Segment。描述Segment的结构叫程序头

1
readelf -l SectionMapping.elf

总的来说,SegmentSection是从不同的角度划分一个elf文件,被称为不同的视图(View),Section对应链接视图,Segment对应执行视图

ELF的程序头表专门用来保存Segment的信息(只有可执行文件和共享库文件有,目标文件没有)。程序头表也是结构体数组。

堆和栈

栈和堆也是以VMA的形式存在的。而已通过查看 /proc 来查看进程的虚拟空间分布。

一个进程基本上可以分为以下几种VMA:

  • 代码VMA:只读,可执行,有image。
  • 数据VMA:可读写,可执行,有image。
  • 堆VMA:可读写,可执行,无image,匿名,可向上扩展。
  • 栈VMA:可读写,不可知性,无image,匿名,可向下扩展。

堆的最大申请数量收到操作系统版本,程序本身大小,用到的库数量,大小,程序栈数量,大小等不同。

进程在启动以后,程序的库部分会把堆栈里的初始化信息中的参数信息传递给main()函数,也就是argcargv,分别对应命令行参数数量和命令行参数字符串指针数组。

Linux内核装载ELF过程

首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()执行指定ELF文件,原先bash进程继续返回等待刚才新进程结束,然后继续等待用户输入命令。

execve() -> sys_execve() -> do_execve()do_execve()先判断文件格式(魔数),然后搜索合适的可执行文件装载处理过程。

sys_execve()系统调用从内核态返回用户态时,EIP寄存器直接跳转到了elf程序入口地址,装载完成。

Windows PE的装载

PE文件中,所有段的起始位置都是也的倍数,如果不是页的整数倍,那么在映射时向上补齐到页的整数倍。

装载过程:

  • 先读取第一页,包含DOS头,PE文件头和段表
  • 检查目标地址是否可用
  • 使用段表中提供的信息一一映射
  • 装载所有需要的DLL
  • 解析符号
  • 根据参数初始化堆和栈
  • 建立主线程并启动进程

5. 可执行文件的装载与进程【《链接、装载与库》学习笔记】
http://blog.bluspace.ren/2025/12/04/5. 可执行文件的装载与进程/
作者
Blauter
发布于
2025年12月4日
许可协议