Skip to content

操作系统-段页式管理

[!cite] > 4.1 为什么要有虚拟内存? | 小林coding

1. 分段 Segmentation

  • 分段是最早时期提出的内存管理的方法之一,其所谓的“段”,是逻辑意义上的段,譬如“代码段”、“数据段”、“堆段”、“栈段”等等,每一个段,都对应逻辑意义上一个功能区域。
  • 段内是连续的,但是段和段之间,并非连续,其概览如下图。

image.png

  • 从虚拟地址到实际地址,段通过“段表”维护了虚拟内存和物理地址之间的映射。
  • 换言之,通过段基地址和偏移量,就可以计算出具体的物理地址。

  • 分段的好处在于逻辑意义清晰,而且一定程度上确实实现了资源的隔离和硬件资源的访问控制
  • 但是依然存在一些弱点,其比较显著的两大缺陷如下:
    1. 内存碎片的问题
    2. 内存交换的效率不足

1.1 段式管理的内存碎片

  • 内存碎片可以分成内部和外部两种,所谓内部碎片,就是碎片产生在连续内存空间的内部,外部碎片即碎片产生在连续内存空间的外部
  • 段式管理基本上不会产生内部碎片,但是其基于逻辑的划分方式,会导致外部碎片的产生,因为一段连续地址可能被段式管理分成很多截了,如果其中某一截回收,其他空间也无法连续。
  • 其原理如下图所示

image.png

1.2 段式管理降低效率

  • 为了解决内存碎片的问题,常见的策略就是使用 swap 来扩展逻辑意义上可以利用的内存空间。
  • 但是段式管理,每次 swap 的都是一个很大的逻辑段,IO 的负担比较大,每次都要把一大块容量写到磁盘上,其效率自然不会高。

2. 分页管理 Paging

2.1 分页管理的基本思想

  • 基本思想很简单:化整为零,不再做逻辑意义上的区分(有点类似于曹冲称象)
  • 将整个内存空间,全部切分成原子化的单位(比如 Linux 系统就是 4KB 大小的内存块),然后将原子化的单位和逻辑意义上的地址做一一映射,形成所谓的“虚拟内存”。
  • 那么,很显然,“物理地址-逻辑地址”这样的一个映射关系,总归是需要一个数据结构去维护它的。
  • 其映射的方式就是页表。页表里面的一项,就维护了 4KB 的物理內存的放置。 image.png

2.2 页表的设计

2.2.1 内部碎片的产生

  • 每个页表的项都是 4KB,它们的排列方式是紧密的,也就不存在外部碎片。
  • 但是页表分配的单位是原子化的,即使空间的大小不足 4KB,也会按照 4KB 去做分配,因此会产生内部内存碎片。

2.2.2 按需引入的 Swap 策略

  • 如果内存空间不足,和外部磁盘进行 Swap 的时候,不再和段式管理类似,将整个逻辑空间和外部进行交换。
  • 程序在运行的时候,写到外部磁盘上面的只是几个页,不会占用过多的时间,因此,内存交换的效率比较高

image.png

2.2.3 内存寻址方式

  • 在分页机制下,虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图。

image.png

  • 虚拟地址通过页号+页内偏移量组成,通过 页号 找到页表中对应的项,然后该项记录了物理页号,读取到物理页号之后,通过页内偏移量计算出实际的物理地址。
  • 总结一下,对于一个内存地址转换,其实就是这样三个步骤:
    1. 把虚拟内存地址,切分成页号和偏移量;
    2. 根据页号,从页表里面,查询对应的物理页号;
    3. 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

image.png

2.2.4 缺页中断

[[操作系统-缺页中断]]

2.2.5 多级页表:降低空间开销

2.2.5.1 单级页表的空间开销预估
  • 操作系统为 32 位,页大小为 4KB,内存为 4GB
  • 所谓 32 位,就是内存虚拟地址是由32个bit组成的,这 32bit,需要存放页表号+偏移量。一般来说,单级页表需要 20bit 去放置页表号。
  • 物理内存一共 4GB,用 4KB 的原子化的块去映射,一共就是 1024 * 1024 个块,差不多 一百万 个块,每个块需要一个表项,那么表项需要占用 4个字节(1024*1024=2^2020 个字节用来存放物理页表号,剩下的存放一些额外信息),那么,至少需要 4 * 100 万的空间存放页表,也就是 大约4MB的空间专门用来放置页表。
2.2.5.2 多级页表如何解决空间
  • 32bit 的内存地址当中,需要 20bit 放置页表号,但是如果将页表拆成多级的,其映射如下图所示

image.png

  • 粗略计算一下,映射 4GB 的内存空间,需要 4KB 的一级页表空间,4MB 的二级页表空间。
  • 看上去空间开销更大了。
  • 但是,仅仅依靠 4KB 的一级页表,就可以覆盖全部的内存映射关系了,如果某个地址没有用到,就不必开二级页表去管理了,换言之,二级页表的空间开销是按需消费的
  • 比如物理内存用了 20%,那么一级页表 4KB,加上 4*0.2=0.8MB 大小的二级页表空间会被消费,那么,可以节约较多的空间。
2.2.5.3 64 位操作系统的4级页表

image.png

  • 原理上和二级页表类似,只有全局页表目录需要加载,其他的页表目录都是按需引入的

2.2.6 TLB 缓存加速

  • 将常用的页表项放到一个更多的地方,那么,对于计算机而言,比内存还快的地方,就是 CPU 缓存了。

  • TLB(Translation Lookaside Buffer) 又称快表,本质上就是常用页表项的 CPU缓存image.png

  • 有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。

  • TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。

3. 段页结合管理

3.1 优势

  • 希望能够同时利用分段管理的逻辑性分页管理的高效率
  • 便于后续的管理。降低成本的开销,同时提高了内存的利用率。

3.2 具体实现方式

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;
  • 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页;
  • 地址结构就由段号、段内页号和页内位移三部分组成。

image.png

  • 寻址方式如下:
  1. 先读取虚拟地址当中的段号,通过段号找到具体的段
  2. 在该段中,通过段内页号,找到具体的页
  3. 通过页内偏移量,找到实际的物理地址