linux内存管理(二)----硬件分页机制

上一章我们学习了操作系统的分段机制,将程序分成不同的段进行管理,我们编程访问内存地址时,访问的其实是操作系统抽象给我们的虚拟地址,通过段基址:段偏移的方式访问内存虚拟地址,极大了简化了程序员的编程结构,解决了之前操作系统存在的两个问题:

  • 地址空间没有隔离
  • 程序运行的地址不确定

但是分段机制也存在严重的问题,在分段的映射方法中,并没有解决内存使用效率的问题。如果应用程序过多,或者内存碎片过多,又或者曾经被换出到硬盘的内存段需要再重新装载到内存,可内存中找不到合适大小的区域,要如何解决这个问题,就引入了分页机制。

1. 分页实现原理

分页的基本方法是将地址空间等分成某一个固定大小的页;每一页大小由硬件来决定,或者是由操作系统来决定(如果硬件支持多种大小的页)。

  • 1.将进程的逻辑地址空间分成若干个大小相等的片,称为页面或页
  • 2.内存空间分成与页大小相等的若干个存储块,称为物理块或页框
  • 3.在为进程分配内存时,以块为单位,将进程中的若干页分别装入多个可以不相邻的块中

关于进程分页,当我们把进程的虚拟地址空间按页来分割,常用的数据和代码会被装在到内存;暂时没用到的是数据和代码则保存在磁盘中,需要用到的时候,再从磁盘中加载到内存中即可.

1.1 硬件分页机制

对于最简单的分页机制,硬件上使用一级页表的方式是最简单的,访问效率也最高,页面的大小一般为 4KB。为了能够定位和访问每个页,需要有个页表,保存每个页的起始地址,再加上在页内的偏移量,组成线性地址,就能对于内存中的每个位置进行访问了,其访问流程图如下:
linux内存管理(二)----硬件分页机制
虚拟地址分为两部分,页号§和页内偏移(o)。页号(用高 20 位表示)作为页表的索引,页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移(低 12 位)的组合就形成了物理内存地址。一级页表这么简单,只要经过一次的地址转换就能找到对应的物理地址,访问效率应该是最好的。

  • 我们假设在32位环境下,虚拟的地址空间为4GB,如果采用一级页表,采用4KB为一个页,那就需要1M个页表。每一个页表需要4个字节来存储,那么整个4GB的地址空间的映射就需要4MB的内存来存储映射表。如果每个进程都有自己的映射表,100个进程就需要400MB的内存,对于内核来说,确实有点大。
  • 这个问题在64位体系结构下, 情况会更加糟糕. 而每个进程都需要自身的页表, 这导致系统中大量的所有内存都用来保存页表。

1.2 多级页表

对于页表中所有页表项必须提前建好,并且要求是连续的。如果不连续,就没有办法通过虚拟地址里面的页号找到对应的页表项了。总之,我们不需要一次性全部建立好页表,我们要动态建立,所以有了多级页表。

总之,为减少页表的大小并容许忽略不需要的区域, 计算机体系结构的就使用了多级页表,下面以二级页表,看硬件上怎么实现的。
linux内存管理(二)----硬件分页机制

  • 第一级表称为页目录,存放在一页 4K 大小的页面中,具有 2^10 个 4 字节长度的表项。 这些表象指向对应的二级表。 线性地址的最高 10 位(31-22)用作以及表中的索引。
  • 第二级称为页表,长度也是 4K 大小的一个页面,最多有 1K 个 4 字节的表项。 每个 4 字节的表项含有相关页面的 20 位物理基地址。 二级页表使用线性地址的中间 10 位(21-12)作为表项索引值,以获取含有页面 20 物理地址基地址的表项。 该20位页面物理基地址和线性地址中的低12位(页内偏移)组合在一起就得到了分页转换过程的输出值,即对应的的最终物理地址。

对于给定的线性地址,CR3 寄存器指定页目录表的基地址。线性地址的高10位用于索引这个页目录表,以获得指向相关第二级页表的指针。线性地址空间中间10位用于索引二级页表,以获得物理地址的高20位。线性地址的低12位直接作为物理地址的低12位,从而组成一个完整的32位物理地址。

那么二级页表怎么解决页表过大的问题呢?我们假设只给这个进程分配了一个数据页。如果只使用页表,也需要完整的 1M 个页表项共 4M 的内存,但是如果使用了页目录,页目录需要 1K 个全部分配,占用内存 4K,但是里面只有一项使用了。到了页表项,只需要分配能够管理那个数据页的页表项页就可以了,也就是说,最多 4K,这样内存就节省多了。

页目录和页表的表项格式如下图所示,其中位32-12含有物理地址的高20位,用于定位物理地址空间中一个页面(也叫页帧)的物理基地址。表项的低 12 位含有页属性信息。

linux内存管理(二)----硬件分页机制

linux内存管理(二)----硬件分页机制

上图就是页目录项和页表项的格式。可以看出,由于页表或者页的物理地址都是4KB对齐的(低12位全是零),所以上图中只保留了物理基地址的高20位(bit[31:12])。低12位可以安排其他用途。

  • 【P】存在位,表示该页是在内存还是在磁盘。为1表示页表或者页位于内存中。否则,表示不在内存中,必须先予以创建或者从磁盘调入内存后方可使用。
  • 【R/W】:读写标志。为1表示页面可以被读写,为0表示只读。当处理器运行在0、1、2特权级时,此位不起作用。页目录中的这个位对其所映射的所有页面起作用。
  • 【PWT】:缓冲写策略。Page级的Write-Through标志位。为1时使用Write-Through的Cache类型;为0时使用Write-Back的Cache类型。当CR0.CD=1时(Cache被Disable掉),此标志被忽略。对于我们的实验,此位清零。
  • 【PCD】:禁止缓存位。Page级的Cache Disable标志位。为1时,物理页面是不能被Cache的;为0时允许Cache。当CR0.CD=1时,此标志被忽略。对于我们的实验,此位清零。
  • 【D】:修改位。该位由处理器固件设置,用来指示此表项所指向的页是否写过数据。
  • 【A】:访问位。该位由处理器固件设置,用来指示此表项所指向的页是否已被访问(读或写),一旦置位,处理器从不清这个标志位。这个位可以被操作系统用来监视页的使用频率。

正常来说, 对于32位的系统两级页表已经足够了, 但是对于64位系统的计算机, 这远远不够.

首先假设一个大小为4KB的标准页, 所以offset字段需要12位.这样线性地址空间就剩下64-12=52位分配给页中间表Table和页目录表Directory。如果我们现在决定仅仅使用64位中的48位来寻址(这个限制其实已经足够了, 2^48=256TB,即可达到256TB的寻址空间)。剩下的48-12=36位被分配给Table和Directory字段, 即使我们现在决定位两个字段各预留18位,那么每个进程的页目录和页表都包含218个项, 即超过256000个项.

基于这个原因, 所有64位处理器的硬件分页系统都使用了额外的分页级别. 使用的级别取决于处理器的类型

2. 分页机制的缺点

分页解决了分段机制的效率问题,同时避免了外部碎片和紧缩,不仅如此,分页还避免了将不同大小的内存块匹配到交换空间的问题,在分页引入之前采用的内存管理方案都有这个问题。由于比早期方法更加优越,各种形式的分页为大多数操作系统采用,包括大型机的和智能手机的操作系统。实现分页需要操作系统和计算机硬件的协作。

  • 1.由于分页机制支持最小的物理内存为4KB的内存容量,对于系统内存来说还是大了,如果每个非常小的进程都分配4KB内存的话,那么10个小进程就相当于4MB了。这样的大小可能对于所需内存容量小的进程来说是个浪费,但是对于分段机制来说,这个内存空间小很多。
  • 2.更重要的是,多级页表虽然解决了分段机制的空间效率问题,但是页表存放在主存中,因此程序每次訪存至少须要两次:一次访问获取物理地址,第二次访问才获得数据。内存访问的速度就减半。在大多数情况下,这种延迟是无法忍受的。

对于这种情况,硬件又基于页表的访问局限性设计了TLB来解决这个问题,下一章会针对这个问题进行讨论。