进程虚拟内存
虚拟地址空间
各个进程的虚拟地址空间起始于 0,一直到 TASK_SIZE - 1,其上是内核地址空间。在 IA-32 系统上地址空间的范围是 4GB,按照默认比例 3:1 来划分的话,内核分配 1GB,各个用户进程可用的部分为 3GB。用户程序只能访问整个地址空间的下半部分,不能访问内核部分,同时用户进程也不能操作另一个进程的地址空间(除非共享内存),因为后者的地址空间不可见。
进程虚拟地址空间由多个不同长度的段组成,用于不同的目的,必须分别处理。例如大多数情况下,不许修改 text 段(代码段),但是可以执行它。另外,必须可以修改映射到地址空间的文本文件内容,但是不可以执行它。进程虚拟地址空间中一般都包含如下不同的段:
- 当前运行代码的二进制代码。该代码通常称之为 text,所处的虚拟内存区域称之为 text 段。ELF(Executable and Linkable Format)二进制文件映射到地址空间后,该区域长度不变,边界由 start_code 和 end_code 标记。
- 程序使用的动态库的代码。
- 存储全局变量和动态产生的数据的堆。start_brk 标识了堆的起始位置,其长度在运行阶段会发生变化。
- 用于保存局部变量和实现函数过程调用的栈。
- 环境变量和命令行参数的段。由 arg_start,arg_end,env_start,env_end 标识。
- 将文件内容映射到虚拟地址空间中的内存映射。
在进程启动时可以指定 PF_RANDOMIZE,如果指定了该标志,则内核不会为栈和内存映射选择固定的起始点,而是每次都在一个小范围内随机,这有助于防止缓冲区溢出攻击。
![e0165e1814177fa019cf8d224b278610.png](https://img-blog.csdnimg.cn/img_convert/e0165e1814177fa019cf8d224b278610.png)
每个体系结构都制定了一个特殊的起始地址,IA-32 起始于 0x08048000,在 text 段的起始地址与最低的可用地址之间有大约 128 MB 的间距,用于捕捉 NULL 指针。其他体系结构也类似,堆紧随着 text 开始,向上增长。栈起始于 STACK_TOP,如果设置了 PF_RANDOMIZE,则起始位置会有一个小的随机量。大多数体系结构中 STACK_TOP 等于 TASK_SIZE,即用户地址空间的最高可用地址。进程的参数列表和环境变量都是栈的初始数据。在 2.6.7 之后,栈的最大长度遭到限制,因此内存映射区域从栈的下方开始(包含一个安全带),自顶向下拓展,mmap 区域和堆相对拓展,直至耗尽地址空间中的剩余区域。
地址转换
在古老的 x86 体系处理器上,刚开始只有 20 根地址线,寻址寄存器是 16 位,我们知道 16 位的寄存器可以访问 64K 的地址空间,如果程序要想访问大于 64K 的内存,就需要把内存分段,每段 64K,用段地址 + 偏移量的方式来访问,这样使 20 根地址线全用上,最大的寻址空间就可以到 1M 字节,这在当时已经是非常大的内存空间了。在这种段式内存管理机制中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,也就是是机器语言指令中,用来指定一个操作数或是一条指令的地址。一个逻辑地址由两部分组成,段标识符: 段内偏移量。段标识符是由一个 16 位长的字段组成,称为段选择符。其中前 13 位是个索引号,后面 3 位包含一些硬件细节。
![b630f5b668d331466cd048b3337a9cb7.png](https://img-blog.csdnimg.cn/img_convert/b630f5b668d331466cd048b3337a9cb7.png)
不过在现代的 CPU 架构中,段式内存管理显然有些多余,因为现代 CPU 的寻址寄存器已经能够覆盖完整的虚拟地址空间,但是为了兼容老旧的设备,Linux 还需要保存这个段式管理机制,但是对于现代 CPU 架构,Linux 会有一个伪段式内存管理机制,这个伪造的段内存管理器很简单,输入的段地址会原封不动的输出作为线性地址(虚拟地址)。
我们知道页表用于建立用户进程的虚拟地址空间(也就是前面说的线性地址)和系统物理内存(内存、页帧)之间的关联。前面结构主要用来描述内存的结构(划分为结点和内存域),同时指定了其中包含的页帧的数量和状态(使用中或空闲)。页表用于向每个进程提供一致的虚拟地址空间。应用程序看到的地址空间是一个连续的内存区,而页表将连续的虚拟地址空间映射到不连续的物理地址空间。该表也将虚拟内存页映射到物理内存,因而支持共享内存的实现(几个进程同时共享的内存),还可以在不额外增加物理内存的情况下,将页换出到块设备来增加有效的可用内存空间。
![30b076291e57af818c813b179e37a425.png](https://img-blog.csdnimg.cn/img_convert/30b076291e57af818c813b179e37a425.png)
- pgd用于全局页目录项。
- pud用于上层页目录项。
- pmd用于中间页目录项。
- pte用于直接页表项。
- offset 用于页内偏移。
内核内存管理总是假定使用四级页表,而这在 IA-32 系统中是不对的,因为它只使用两级分页系统。因此,三四级页表必须在体系结构的代码中进行模拟。
内存映射
由于所有用户进程总的虚拟地址空间比可用的物理内存大得