虚拟内存管理
虚拟内存和物理内存的映射方式
内存分段
内存分段会带来两个问题:
- 内存碎片
- 内存交换
内存碎片是因为多个分段不连续,分段间的空间无法被利用
要解决这个问题就需要使用磁盘的交换(Swap)空间
例如上图,当需要载入一个 200MB 的新程序时
- 将 256MB 的程序先写入磁盘的 Swap 空间,然后重新载入内存
- 使其紧靠 512MB 部分的内存
- 这样就能将两个不足 200MB 大小的内存碎片,整合为一个足够支撑 200MB 程序的内存了 如果需要重新加载的程序内存比较大,写入磁盘、写回内存的性能开销比较大,内存分页就解决了这个问题
内存分页

- 内存分页将物理内存划分为多个页,linux 中每个页的大小是 4KB
- 通过内存中的页表,管理虚拟内存页和物理内存页的映射
- CPU 中的 内存管理单元(MMU) 负责通过页表,将虚拟内存地址转换为物理内存地址
- 当进程访问的虚拟内存地址在页表中不存在时,系统会产生一个 缺页异常、进入系统内核空间、分配物理内存地址、更新进程页表,回到用户空间后恢复程序运行
内存交换做出的优化
- 由于预先将内存分为多个页,每个页紧密排列,所以不存在 外部内存碎片问题 但是如果一个程序需要的内存小于一个页的大小,依旧会分配一个页给程序,就会导致 内部内存碎片问题
- 如果内存空间不够 操作系统会进行 换出(Swap Out) 操作:把「最近未被使用的」内存页释放,写入 Swap 分区; 后续需要的时候再进行 换入(Swap In) 操作:将 Swap 分区的数据重新加载回内存。
- 这样一次内存交换的只有一个或者几个页,相较于内存分段,内存交换的效率大幅提高 更进一步的,当加载一个程序时,在建立虚拟内存到物理内存的映射后,可以先不将完整的程序加载到物理内存,而是在程序运行中,需要用到虚拟内存页中的指令和数据时,再读取磁盘将其加载到物理内存中
单个页表的映射方式

- 虚拟地址包含页号,和页内偏移量
- 根据页号,通过页表找到目标物理页号
- 通过页内偏移量,定位目标内存地址
多级页表
每个页 4KB,在单个页表的情况下,要覆盖 4GB 以上的内存地址,页表需要有百万级数量的页表项,每个页表项占用 4B,这会导致页表本身就会有很高的内存占用
多级页表的情况下,以二级页表为例,一级页表覆盖完整的虚拟内存地址,如果一级页表中的一些页表项没有被用到,就可以不用创建二级页表,这样就能节省很大的页表内存占用

现代的 64 位系统内存很大,所以采用四级分页:
- 全局页目录项 PGD(Page Global Directory);
- 上层页目录项 PUD(Page Upper Directory);
- 中间页目录项 PMD(Page Middle Directory);
- 页表项 PTE(Page Table Entry);

TLB
多级页表节省了页表的内存占用,但也提高了虚拟地址转换为物理地址的时间开销,于是引入 页表缓存(TLB, Translation Lookaside Buffer), 也叫 转址旁路缓存,用于缓存最常用的几个页表项
TLB 是 CPU 内部,MMU 旁边的,一块高速小容量存储器
