Hi~朋友,码字不易,点点关注
摘要
段式内存管理缺点 分页机制 如何通过页表获取真实物理地址 二级页表 二级页表如何获取真实物理地址 快表
段式内存管理缺点
在前面的文章中,我们知道了CPU访问内存的方式,即“段基址:段内偏移地址”的方式,在我们使用这种方式去访问内存时是访问的真实的物理内存。
当多个进程同时在运行时,每个进程的内存都会经历从磁盘换入内存,从磁盘换出内存的过程,为什么需要这种换入换出操作呢,原因是我们的内存空间是有限的,为了给活跃的进程腾出运行空间我们必须要将不活跃进程所占据的内存从物理内存中移出。
由于目前我们的段式访问都是直接针对的物理地址,物理地址是线性且连续的,那么我们在进行内存换出时会有如下问题:
如果本身内存过小,将无法进行换入换出操作 另一个问题如下图

我们的进程C需要11M的内存,但是由于空闲区域F1和F2都不能满足进程C需要的内存,同时由于进程A和B都是活跃进程,因此不可以被腾出,于是进程C必须等待进程A或进程B腾出相应的内存空间,但是这种等待是不可控的。
F1和F2两块区域的和是足够进程C的内存,但由于我们为进程C分配的内存是连续的,因此这种段式内存的利用率是低下的。
为了解决这种分段模式下进程的线性地址等同于物理地址问题,我们必须要将线性地址和物理地址解绑,解绑以后线性地址连续,但物理地址可以不连续。
这就需要借助分页机制。
分页机制
CPU在不打开分页机制的情况下,是按照默认的分段方式来进行的,即通过段基址和段内偏移地址计算出来的内存地址是真实的物理地址。
CPU打开分页机制以后,上述地址便不再是真实的物理地址,而是虚拟地址,该虚拟地址需要经过转换才能变为真实的物理地址。

右侧分页机制下访问内存需要通过页表进行检索,将通过段基址和段内偏移地址计算出来的线性地址(虚拟地址)转换为真实物理地址。
分页机制主要有两个作用:
将线性地址转换成物理地址 用大小相等页代替大小不等的段

页表中能够包含很多页表项,大小固定为4字节,页表中记录的是物理地址。
开启分页模式以后,每一个页的大小固定为4KB,1个4KB的x地址空间对应一个4KB的物理地址空间。
如何通过页表获取真实物理地址
如何通过页表获取真实物理地址,首先我们得知道页表的存储位置在哪?
在分页机制打开,我们需要将页表地址加载到控制寄存器cr3中,保存的是物理地址,以后通过cr3我们就可以获取到页表的地址。
一个页表项对应一个页,虚拟地址的高20位作为页表项的索引,由于每个页表项占用4字节,所以这高20位的索引值乘以4然后再加上cr3中的页表地址,得到的结果即为该页表项的物理地址。
上述我们已经可以定位到页表项的物理地址了,那么下面就是如何从页表项中获取我们需要访问的物理地址。
从页表项中取回实际要访问的物理地址,然后将其与虚拟地址的低12位相加,即可得到我们需要访问的物理地址。由于每个页固定位4KB,所以虚拟地址的低12位正好可以表示页内偏移。
mov ax, [0x1234]
复制

假设我们的段基址为0x0,段内偏移为0x1234,CPU的段部件通过计算获得虚拟地址0x1234。由于我们假设分页机制已经打开,所以虚拟地址0x1234被送入页部件,页部件首先发现高20位为0x00001,即页表项的索引为1,将该索引值乘以4然后加上cr3中的物理地址便可得到页表项的物理地址,从该物理地址处读取页表项映射的物理地址为0x9000,虚拟地址的低12位为0x234,即页内偏移地址为0x234,将页表项映射的物理地址为0x9000和页内偏移地址相加,所得的和0x9234即我们需要访问的真实物理地址。
我们可以从页表项中获取映射后的真实的物理地址,那么这个物理地址是怎么存储的呢?这就关系到页表项的数据结构,如下图:

从上图可以看出,高20位用来记载物理地址。
P:页是否存在物理内存中,1存在,0不存在 US:若为1,表示处于User级,任意特权(0、1、2、3)的程序都可访问该页,0表示只允许特权级为0、1、2的程序访问该页 D:脏页位,当CPU对一个页面进行写操作时,会设置该字段为1 G:内存地址转换除了需要经过页表,后面我们还会讲到经过页目录表,所以为了将提高速度,我们会将转化后的结果存储在快表(TLB)中。如果为1表示全局页,将会在TLB中保存,0则不保存。 PWT:如果为1,表示该页不仅是普通内存,还是高速缓存 PCD:如果为1,表示启用高速缓存,如果为0表示该页禁用高速缓存
二级页表
在了解二级页表之前,我们说一下一级页表的特点:
一级页表最多容纳1M(4GB/4KB = 1048576)个页表项,每个页表项是4字节,如果页表项全满的话,便需要占据4M空间 一级页表中的所有表项必须要提前建好,原因是操作系统要占用4GB虚拟地址空间的高1GB,用户进程占用低3GB 每个进程都有自己的页表,进程越多,页表占用空间越大。
综上所述,一级页表必须提前建好,随着进程数增多,页表占用空间越大,如何解决上述问题?那么我们就需要页表在我们需要的时候动态增加,不需要一次性建立好,这种解决方案就是通过二级页表。
一级页表是将1M个页放置到一张页表中,二级页表是将1M个页平均放置到1K个页表中,每个页表包含1K个页表项,每个页表项4字节,即二级页表这个大小恰好是4KB大小,即一个页。
由于我们将一张页表拆成了多张页表存放,因此需要一个统一管理这些页表的地方,该地方的名称叫页目录表,页目录表和页表以及真实物理空间的关系如下图:

在页表项中分配的物理地址是无规律可循的,操作系统负责进行分配和释放。
二级页表如何获取真实物理地址
二级页表需要将虚拟地址拆解成3部分,分别为
高10位:用来定位页目录表中的一个页目录项(页目录项中包含页表的物理地址) 中间10位:用于在某个页表中定位页表项 低12位:页内偏移量
具体的转换过程如下:
段部件生成虚拟地址 用虚拟地址的高10位乘以4,再加上页目录表的物理地址,便是页目录项的物理地址,读取该物理地址处的内容,获得页表的物理地址 用虚拟地址的中间10位乘以4,再加上一步获得的页表的物理地址,便是页表项的的物理地址,读取页表项的内容,便可从页表项的数据结构中获取我们需要访问的物理地址 将该物理地址再加上虚拟地址的低12位,便是最终我们要访问的物理地址
通过页目录项我们可以知道页表项的物理地址,但是我们怎么知道页目录项的物理地址呢?
答案还是cr3寄存器,由于我们开启二级页表以后,可以在页目录项中获取页表的位置,因此cr3寄存器会存储我们页目录表的地址,因此CR3寄存器也称为页目录基址寄存器,结构如下图:

快表TLB
分页机制虽然足够灵活,但是却多了很多转换的步骤,这些步骤对于CPU来说还是比较麻烦的,如何可让CPU通过一个虚拟地址直接获取到物理地址呢,不再去查页目录项查页表呢?答案就是缓存。
这个缓存的名称就是快表TLB,结构如下图:

TLB中存储的是虚拟地地址高20位到物理地址高20位的映射关系。
由于TLB是告诉缓存,容量较小,因此只有页表项中标志位P为1的页表项才有资格在TLB中,如果TLB被装满,需要将很少使用的条目换出。
TLB的维护也是由操作系统完成,关于TLB的更新主要由以下两种方法:
重新加载cr3寄存器,会重新加载整个TLB 使用invlpg指令对指定的缓存条目进行刷新
本期内存管理机制-页表就到这,扫码关注,更多内容我们下期再见!
往期推荐