进程虚拟地址空间
-
程序:静态 的概念,预先编译好的指令和数据的集合的一个文件
-
进程:动态 的概念,程序运行时的一个过程
每个程序被运⾏起来以后,它将拥有⾃⼰独⽴的虚拟地址空间,这个虚拟地址空间的⼤⼩由计算机硬件平台决定,具体地说是由CPU的位数决定的
PAE - 物理地址扩展
PAE(物理地址扩展)是一个 32位 操作系统和 CPU 的扩展机制,使得操作系统能够访问超过 4GB 的物理内存。这个扩展是在 32位 CPU 环境下实现的,目的是通过 修改内存的分页方式 来突破 4GB 的限制
-
虚拟地址空间 仍然是 32 位的,因此每个进程的虚拟地址空间不能超过 4GB。操作系统和程序本身依然使用 32位指针 来管理虚拟内存
-
物理地址空间 则可以突破 4GB限制,最多支持 64GB(32位 CPU 使用 PAE 时支持 36 位的物理地址)
如何实现?
-
PAE通过修改分页机制,使得操作系统能够 映射更多的物理内存。具体地,PAE 引入了一个新的分页机制(4级页表),这样可以映射超过 4GB 的物理内存
-
操作系统并不会直接把所有的内存映射到每个进程的虚拟地址空间,而是使用 "窗口映射" 的方式:操作系统 动态地将额外的物理内存映射到虚拟地址空间中的一个窗口,程序通过这个“窗口”访问物理内存。通过改变页表映射,操作系统可以让程序访问到不同的内存区域
装载的方式
程序执⾏时所需要的指令和数据必须在内存中才能够正常运⾏
静态装入
将程序运⾏所需要的指令和数据全都装⼊内存中,可能面临物理内存不足的状况
动态装入
程序⽤到哪个模块,就将哪个模块装⼊内存,如果不⽤就暂时不装⼊,存放在磁盘中
-
覆盖装入
-
程序员 手工将程序分割成若干块
-
编写辅助代码 覆盖管理器,管理这些模块 何时驻留/替换
-
覆盖管理器必须保证前2点
-
调用路径上的所有模块必须在内存中(从任一模块到根模块(main)的路径称为调用路径),从而确保调用模块能够正常返回
-
禁止跨树间调用:不允许模块跨越树状结构调用其他模块
-
模块依赖:多个模块依赖同一模块(如G)时,最好将G放入main模块,确保它在所有相关模块的调用路径上
-
覆盖管理器确保模块在内存中,如果不在内存中,将从磁盘加载(时间换空间)
-
-
简单情况(main调用A和B,A和B之间不互相调用)
![1]()
复杂情况:
-
模块 main 依赖于 模块 A 和 模块 B
-
模块 A 依赖于 模块 C 和 模块 D
-
模块 B 依赖于 模块 E 和 模块 F
![2]()
-
-
页映射
- 页替换算法
从操作系统角度看可执行文件的装载
进程的建立
- 创建⼀个独⽴的虚拟地址空间
⼀个虚拟空间由⼀组⻚映射函数将虚拟空间的各个⻚映射⾄相应的物理空间,那么创建⼀个虚拟空间实际上并不是创建空间⽽是创建映射函数所需要的相应的数据结构,在i386 的Linux下,创建虚拟地址空间实际上只是分配⼀个⻚⽬录(Page Directory)就可以了,甚⾄不设置⻚映射关系,这些映射关系等到后⾯程序发⽣⻚错误的时候再进⾏设置
这一步是 虚拟空间到物理内存的映射关系
- 读取可执⾏⽂件头,并且建⽴虚拟空间与可执⾏⽂件的映射关系
当操作系统捕获到缺⻚错误时,它应知道程序当前所需要的⻚在可执⾏⽂件中的哪⼀个位置。这就是虚拟空间与可执⾏⽂件之间的映射关系
由于可执⾏⽂件在装载时实际上是被映射的虚拟空间,所以可执⾏⽂件很多时候⼜被叫做映像⽂件(Image)
建立 虚拟内存区域(VMA)
参考这篇文章 https://blog.csdn.net/zhoutaopower/article/details/89501437
- 将CPU的指令寄存器设置成可执⾏⽂件的⼊⼝地址,启动运⾏
跳转到可执⾏⽂件的⼊⼝地址(ELF⽂件头中保存有⼊⼝地址)
页错误
上⾯的步骤执⾏完以后,其实可执⾏⽂件的真正指令和数据都没有被装⼊到内存中。操作系统只是通过可执⾏⽂件头部的信息建⽴起可执⾏⽂件和进程虚存之间的映射关系⽽已
当发生 页错误 时
操作系统将查询这个数据结构,然后找到空⻚⾯所在的VMA,计算出相应的⻚⾯在可执⾏⽂件中的偏移,然后在物理内存中分配⼀个物理⻚⾯,将进程中该虚拟⻚与分配的物理⻚之间建⽴映射关系,然后把控制权再还回给进程,进程从刚才⻚错误的位置重新开始执⾏

进程虚拟内存空间分布
ELF文件的链接视图和执行视图
在将⽬标⽂件链接成可执⾏⽂件的时候,链接器会尽量把相同权限属性的段分配在同⼀空间
⼀个“Segment”包含⼀个或多个属性类似的“Section”

堆和栈
操作系统中,VMA(虚拟内存区域)除了用于映射可执行文件的各个“Segment”外,还用于管理进程的地址空间。进程在执行时需要栈(Stack)、堆(Heap)等空间,它们在虚拟地址空间中也以VMA的形式存在,通常栈和堆各自都有一个对应的VMA。

- 第一列: VMA的地址范围
- 第二列: VMA的权限 (r可读,w可写,x可执行,p私有,s共享)
- 第三列: VMA对应的segment在映像文件中的偏移
- 第四列: 映像文件所在设备的主设备号和次设备号
- 第五列: 映像⽂件的节点号
- 第六列: 映像⽂件的路径
最后三个段的⽂件所在设备主设备号和次设备号及文件节点号都是0,则表示它们 没有映射到⽂件中,这种VMA叫做 匿名虚拟内存区域(Anonymous Virtual Memory Area)
vdso是一个特殊的 VMA,它位于内核空间(地址大于 0xC0000000),是一个内核模块,进程通过访问该 VMA 与内核进行通信。
进程的基本VMA划分


段地址对齐
可执⾏⽂件最终是要被操作系统装载运⾏的,这个装载的过程⼀般是通过虚拟内存的⻚映射机制完成的。在映射过程中,⻚是映射的最⼩单位
也就是说可执行文件的的映射是由长度和地址对齐限制的,那么就可能会产生一些内存碎片,比如某个段末尾的一小部分单独占有一张页,就可能出现如下情况

所以,可执行文件应该尽量优化自己的空间和地址安排来减少内存浪费
有一种取巧的办法是,将 段与段之间接壤的部分映射到同一张物理页面,然后创建多个虚拟页面到同一物理页面的映射

进程栈初始化
我们知道进程刚开始启动的时候,须知道⼀些进程运⾏的环境,最基本的就是系统环境变量和进程的运⾏参数。很常⻅的⼀种做法是操作系统在进程启动前将这些信息提前保存到进程的虚拟空间的栈中(也就是VMA中的Stack VMA)
假设我们的环境变量只有两个
HOME=/home/user
PATH=/usr/bin
命令行
prog 123
以下是初始化的栈

栈顶寄存器 esp 指向的位置是初始化后堆栈的顶部,最前面的 4 个字节表示命令行参数的数量(例如两个参数:“prog”和“123”),接着是指向这两个参数字符串的指针,后面是一个 0,然后是两个指向环境变量字符串的指针(分别指向“HOME=/home/user”和“PATH=/usr/bin”),最后是一个 0 表示结束。


posted on
浙公网安备 33010602011771号