2023-06-27 操作系统分页机制的深入浅出

2023-06-27 操作系统分页机制

  1. 首先我们知道,无论在实模式下还是保护模式下,我们的内存访问都是采用的分段寻址(详见:2023-06-23 操作系统实模式、保护模式的深入浅出),似乎仅有分段寻址就已经能满足了我们程序运行的所有需求。既然这样那为什么我们还需要内存分页呢?

    我们先来看看下面一张图:

    在这里插入图片描述

    ​ 这张图中,描述了在分段下多个进程并行的情况(前面说过保护模式支持多进程),其中已经存在了3个进程段A、B、C。假如现在又要运行一个进程D,现在还未使用分页机制。cpu认为“段基址”+“段内偏移”产生的线性地址就是物理地址,又由于编译器生成的可执行文件中段内的线性地址是连续的,所以段加载到内存中其物理地址也要连续以保证指令能够被正常执行。但是内存剩下的连续可用空间并不够装载下D,现在该怎么办呢?

    ​ 下面提供两种常见的解决思路:1)等待进程A、B、C执行完内存中有足够的空间后再装载进程D。2)将进程A、B或者B、C的部分段换出到磁盘,腾出足够的内存空间再装载D。

    ​ 方案1:直接简单几乎不需要对现有程序和机器进行修改,只需要时间等待进程ABC执行结束即可。但是万一其中某个进程程序跑飞了出现死循环那么这一块内存将会一直被占用,D的装载时间将会遥遥无期。所以这种方案显然不太好,不够灵活。

    ​ 再来看看方案2:该方案的核心思想是将不常用的段的换出到磁盘,腾出空间给新进程使用,当老进程又需要该段的时候再从磁盘中读取换入。

    ​ 实际上根据之前的保护模式的内容,读者也许会注意到在保护模式下,对段的访问是通过段描述符进行的。而在段描述符中标志位P为1代表该段在内存中,P位为0则代表段不在内存中,这显然就是为段的换入、换出准备的。

    ​ 接下来简单说一下段换入换出的逻辑:1)换入:假设某个段描述符的P位为1,那么cpu在访问这个段后将会把该段描述符的A位置为1代表最近访问过这个段。如果段描述符的P位为0,则代表段不在内存中,cpu要访问这个段会抛出个NP(段不存在)异常,转到中断描述符表中NP异常对应的中断处理程序去执行,该中断处理程序由操作系统负责提供,利用该中断处理程序可以完成将段从磁盘读取到内存中指定位置并将该段描述符置P位为1,该程序结束后cpu会再次检查,检查通过后继续执行。2)换出:cpu访问过某个段描述符对应的段后会将该段描述符的A位置为1,操作系统每发现段描述符中A位为1就将该为置为0,在一个时间周期内统计每个段清零的次数就可以知道段的使用频率。这样低使用频率的段被操作系统换出到磁盘后,操作系统又会将该段的段描述符的P位置为0。

    (补充内容:这里提一下在操作系统和计算机硬件的发展中有很多构思、理论的实现并不单单靠硬件或者软件某一方来进行实现,相反通常都是这两者互相配合才能完成。比如在段换出中cpu作为硬件一方负责检查段描述符,操作系统作为软件方负责段的换入换出时修改对应段描述符标志位的工作。作为刚接触的人来说常常会因为知识量的不足,搞不明白操作系统与硬件间的联系。当你觉得有很多理论觉得很熟悉但对于如何去实现却如同雾里看花时,一定要花时间去查找、学习、思考逐渐建立起自己的理解。)

    ​ 好了再说回来,方案2看起来很完美,但是在换出的时候由于要保证段地址的连续性,所以需要要整段整段的换入、换出。因此cpu需要等待段从磁盘中换出的时间会随着段的增大而增大,又随着代码量的增长程序段的大小将不可避免的增大,所以段换出成本也越来越大。另外,假如说计算机的物理内存不足以载入哪怕是一个进程中的一个段,那么就无法运行该进程了。这么来看换段虽然比方案1灵活甚至有了具体的实现,但是仍然存在内存空间使用不够灵活(换入换出都以段作为单位)。

  2. 分页机制:对于分段换入换出的缺陷(段越大换入、出成本越大;物理内存比段小则无法正常加载程序),我们仔细思考造成这两个问题的原因是什么?

    ​ 首先在目前的分段寻址下,cpu认为段基址+段内偏移形成的线性地址就是物理地址。而段内的线性地址是由编译器编译出来的,它本身是连续的,这就导致线性地址与物理地址在一一映射的关系下就要使用连续的物理空间去装载完一个完整的段以保证这种连续性,所以段必须完整的换入换出。

    ​ 想要解决这个问题,可以改动编译器,让编译器根据当前物理内存情况在编译时生成对应当前空闲物理内存空间的不连续的段内地址。或者在加载段的时候做手脚,让加载器找出足够大小的不连续物理空间去装载编译好的段。

    ​ 考虑编译器改动需要提前知道内存占用的情况这有点不切合实际,且这样生成的可执行文件具有时间和空间的局限性(生成的可执行文件只能跑在此刻的这台机器上,一旦机器的内存空间的占用情况发生变动就可能装载失败)所以这种办法放弃掉。

    ​ 这样一看,我们只能在装载段的时候做解决。按照这种思路我们应该先解除线性地址与物理地址一一映射的关系以便能够用不连续物理空间去装载连续的段,再重新构建一种新的映射关系满足能从线性地址找到唯一的物理地址—这就是将要介绍的分页机制。

    ​ 接下来我会从宏观的角度上讲解下分页机制下是如何进行的寻址。

    ​ 首先要知道分页机制是建立分段机制的基础上对段基址+段内偏移生成的线性地址做处理,虽然分页机制本质上并不依赖分段机制,但是由于分段基址已经是IntelA32架构骨子里的东西了,没法舍弃,所以分页机制只能建立在分段上。在分页机制下,寻址大致上分两步:1)以分段机制为基础生成线性地址 2)分页机制转换线性地址为真实的物理地址。

    ​ 1)根据指令要访问的段寄存器值和段内偏移查找内存中的全局描述符表或局部描述附表获取段基址,通过段基址+段内偏移生成32位的线性地址,此时该地址不是真正的物理地址所以也被叫做虚拟地址。

    ​ 2)把上一步获取的线性地址分成两部分,一部分是用于查找页的索引另一部分是页内被访问的存储空间的对应偏移量,这样就完成了从线性地址到物理地址的转换。这种转换只是做映射,所以不会对线性地址值进行改变。

  3. 根据上文,我们现在知道到了分页机制的工作原理。下面开始介绍如何将分页机制进行具象化,即软硬件是如何配合实现的。

    (注意,以下的K、M、G均为2进制计数单位,分别为2的10、20、30次方)

    一级页表

    首先介绍的是一级页表,在物理上80386有 32根地址线,寻址空间能到4GB(2的32次方),假设我们定义4KB大小的内存空间为一张页,那么4GB的内存空间大小能够分出1M个页。第一页能表示0 ~ 4K-1个字节,第二页表示4k ~ 8k-1个字节…以此类推。为了能够快速定义数据在那一张页,需要建立页的索引。这就像我们在看书时想要看哪一部分的内容就可以去看目录一样,根据页表里的数据(也叫做页表项—PTE)去快速找到对应的页。(这里注意一点,所谓页表本质上也是一个类似描述符表的内存中的数据结构,需要一个或者多个物理页存放,只不过这些页里放的不是普通数据而是其他页的索引,所以这些页是一个页表的一部分。)

    在页固定大小为4KB的情况下4GB的空间可以分出1M个页,需要1M个页表项去索引。因为每一个页表项记录的是某一个页32位的起始物理地址(占用4个字节),而一个页大小为4KB,所以可以容纳1K个页表项,所以使用全部4GB的空间就需要连续的1K个页做页表。将第一个页表的物理地址记录到寄存器中,将段部件生成的32位线性地址(虚拟地址)的高20位作为页表项索引。由于一个页表项占用4个字节,所以: 线性地址高20位表示的值 * 4 + 寄存器中页表物理地址值 = 页表项的物理地址值。根据页表项中记录着的页起始地址找到对应的页,线性地址的低12位作为该页的页内偏移地址:物理页地址+线性地址的低12位 得到最终的物理地址。

    例:在如下图中的一级页表展示了线性地址0x01004000是如何转换成物理地址0xFFFFF000并获取数据0x1234的逻辑过程

    在这里插入图片描述

    转换的逻辑展示完,下面来说一说页表项(占4字节)的结构。你可能会有疑惑页表项里不就是一个页的32位物理地址吗,这有什么可分析的?但实际上页表项只有高20位用于定位页的物理地址,因为页大小统一4KB所以把这高20位左移12位就能得到页的32位物理起始地址,剩下的低12位用于标注对应页的属性。页表项的结构如下:

    在这里插入图片描述

    第0位:P,存在位。该页存在于物理内存中该位为1,不在则为0.

    第1位:RW,读写位。1代表该页可读可写,0可读不可写。

    第2位:US,普通用户/超级管理位。1处于User级可任意特权级(0、1、2、3)程序可访问该页,0处于Supervisor级,允许处于特权级0、1、2程序可访问,3不可访问。

    第3位:PWT,页级通写位(页级写透位)。1代表该项采用通写方式表示该页不仅是普通内存还是高速缓存,用于改善该页访问效率,反则且一般为0。

    第4位:PCD,页级高速缓存禁止位。1表示该页启动高速缓存,0禁止将该页缓存(一般为0)。

    第5位:A,访问位。1表示该页已经被cpu访问过,该位由cpu置为1,0表示尚未被cpu访问。(os每隔一段时间就将该位清零,同时统计每页清零的次数作为页的使用频率)当内存空间不足时os会将低使用频率的页换出到磁盘中,下此要访问该页时,由于该页不存在物理内存中触发pagefault异常执行中断处理程序从磁盘中再次读入该页到物理内存。

    第6位:D,脏页位。当cpu对一个页进行写操作时,会将对应的页表项的该位置为1,并不会对相关的页目录项做修改。

    第7位:PAT,页属性表位。能够在页的粒度上设置内存属性,比较复杂一般置为0即可。

    第8位:G,全局位。二级页表内存地址转换一般需要先拆分虚拟地址,再查页目录、查页表,所以为了缩减转换时间,直接将虚拟地址和对应的物理地址保存在TLB(一种高速缓存)中,该位为1代表该页是全局页,表示该页将会一直在TLB中,根据虚拟地址直接给物理地址(TLB容量很小,所以里面只放访问频率较高的页。清空TLB的方式有两种:1. 使用invlpg指定单独清理虚拟地址条目 2. 重新加载cr3寄存器,这会直接清空TLB)。

    第9~11位:AVL,可用位。os根据该位判断该页是否可用,cpu对该位不理会。

    第12~31位:对应页的起始物理地址。

    二级页表

    二级页表是目前多数操作系统采用的内存模型,它解决了一级页表存在的问题:1)被使用的内存会建立对应的页表项,假如整个4GB空间都有已经使用了那么页表项的占用空间达4MB。 2)一级页表的页表项在初始时要全部创建好,因为操作系统要使用虚拟地址空间的高1GB空间,用户进程使用低3GB空间。 3)不同的用户进程都有自己的页表,进程越多页表项占用的空间越多(多进程的内存空间问题之后再讨论)。

    在二级页表中,多了一种页叫做页目录,页目录里是数据叫做页目录项(PDE)占四个字节,记录的是某个页表的32位物理起始地址。整个4GB空间仍被分为1M个页,其中每一张页表有1K个页表项能够表示4MB大小的内存空间。而一个页目录恰好能记录1K个页目录项即1K个页表,所以一个页目录就能表示整个4GB大小的内存空间。

    将页目录的起始地址放入寄存器中(在80386中有专门的寄存器去存放这个页目录的起始物理地址—即控制寄存器cr3。)段部件生成32为线性地址,高10位作为页目录项的索引,中10位作为页表项索引,最后12位仍是页内的偏移地址。由于页目录占用四个字节所以:线性地址的高10位 * 4 + cr3值 = 页目录项物理地址,从页目录项中取出页表物理地址 + 线性地址中10位 * 4 = 页表项物理地址,从页表项中取出页的物理地址 + 线性地址低12位 = 最终数据物理地址。

    例:在如下图中的二级页表展示了线性地址0x01006000是如何转换成物理地址0xFFFFF000并获取数据0x1234的逻辑过程

    在这里插入图片描述

    同理,我们又来分析下页目录项(占4字节)的结构:

    在这里插入图片描述

    大部分相同,但是第7位暂时略过(后面有空了再来补充)。

  4. 多级页表,原理与一级到二级的处理方式相同,可以理解为又建立了一个页目录表索引的内存中数据结构。用于快速定位到一个页目录表,再根据页目录项定位到页表…对于32位,4GB的机器来说目前二级页表已经够用了。

  5. 总结

    • 分页机制虽然建立在分段机制上但这其实只是为了软硬件的兼容,实际上分页机制并不依赖分段
    • 分页机制本质上是将内存空间分为同等大小的内存块(页),从而建立了一种对于这些块的的快速索引,相比于分段,这些内存块(页)的大小可以灵活的进行调整,且换入换出更方便,目前通用的页大小为4KB。
    • 建立在分段机制上分页机制将段部件生成的线性地址(即虚拟地址—需要转换后才是物理地址),进行拆分处理,把拆分后的地址作为索引获取最终物理地址。
    • 多级页表的机制相当于是套娃,一般来说4GB大小的内存空间二级页表就足够了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值