一、实验目的
本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现。实验原理最大的区别是在设计了如何在磁盘上缓存内存页,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。
- 了解虚拟内存的Page Fault异常处理实现
- 了解页替换算法在操作系统中的实现
二、实验内容
本次实验是在实验二的基础上,借助于页表机制和实验一中设计的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现,结合磁盘提供的缓存空间,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。这个实验与实际操作系统中的实现比较起来要简单,不过需要了解实验一和实验二的具体实现。实际操作系统中的虚拟内存管理设计与实现是相当复杂的,涉及到与进程管理系统、文件系统等的交叉访问。
三、实验练习
1.练习0:填写已有实验
将Lab1、Lab2中完成的代码复制到Lab3中。
使用diff命令,查看lab2和lab3中文件的区别,然后按照输出的提示,将lab2中的代码复制到lab3中(lab1中的代码已经在上次实验复制到了lab2中)。
涉及到的文件有lab1中修改的kern/debug/kdebug.c和kern/trap/trap.c文件,以及lab2中修改的kern/mm/default_pmm.c和kern/mm/pmm.c文件。
2.练习1:给未被映射的地址映射上物理页(需要编程)
完成do_pgfault(mm/vmm.c)函数,给未被映射的地址映射上物理页。设置访问权限的时候需要参考页面所在VMA的权限,同时需要注意映射物理页时需要操作内存控制,构建所指定的页表,而不是内核的页表。注意:在LAB2 EXERCISE 1处填写代码。执行make qemu后,如果通过check_pgfault函数的测试后,会有“check_pfault() successed!”的输出,表示练习1基本正确。请在实验报告中简要说明你的设计实现过程。
① 查看相关的数据结构和函数。
a.查看lab3/kern/mm/vmm.h中的数据结构vma_struct和mm_struct,这两个数据结构描述了ucore模拟应用程序运行所需的合法内存空间。当访问内存发生页缺失异常时,可以获得访问的内存的行为方式(权限,读或写)以及具体的虚拟内存地址,让ucore据此查询此地址,看是否属于vma_struct数据结构中描述的合法地址范围。如果在范围内,则可以根据具体情况请求调页/页换入换出处理;否则,进行报错。
b.vma_struct:描述应用程序对虚拟内存“需求”的数据结构。具体分析如下:
c.mm_struct:包含所有虚拟内存空间的共同属性的数据结构。具体分析如下:
d.与mm_struct和vma_struct相关的函数功能:
e.此外,为了实现虚拟内存管理,lab2中的Page结构体新增了两个变量:pra_page_link和pra_vaddr,用于页替换算法。
② 设计实现过程
a.页访问机制的实现原理
- 当启动分页机制后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型(权限)有错误,就会发生“Page Fault”异常。产生“Page Fault”的主要原因有:
目标页面不存在(页表项为0,即线性地址和物理地址没有建立映射关系)
相应的物理页面不在内存中
访问权限不符合 - 页访问异常,需要处理的事:
先将产生页访问异常的线性地址存入cr2寄存器中,并给出错误码error_code说明页访问异常的具体原因。
ucore OS将其存入struct trapframe中的tf_err中,等到中断服务例程调用页访问异常处理函数do_pgfault()时,再判断具体原因
若不在某个VMA的地址范围内,或不满足正确的读写权限,则判定为非法访问
若范围正确、权限合法,则认为是合法访问,只是缺少虚实映射关系,分配一页并修改页表,完成虚拟地址到物理地址的映射,刷新TLB,最后调用iret重新执行引发页访问异常的那条指令。
若是访问页不在内存中,而是在外存汇总,则将其换入内存并刷新TLB,然后退出终端服务例程,重新执行引发访问异常的那条指令
b.异常处理函数do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)有三个参数,第一个是struct mm_struct变量,保存所使用的PDT、合法的虚拟地址空间(双向链表)以及与后文的swap机制相关的数据;第二个参数是产生“Page Fault”时硬件产生的错误码error_code,用于帮助判断发生“Page Fault”的原因;第三个参数是“Page Fault”的线性地址(保存在cr2寄存器中)。
c.do_pgfault()函数的实现:
先查询mm_struct中的合法的虚拟地址链表,用于确定当前出现“Page Fault”的线性地址是否合法,若合法则继续执行调出物理页,否则直接返回;
再根据error code判断是否出现越权行为(读取权限冲突),接着根据合法虚拟地址产生对应物理页权限;
如果页表项为0,说明尚未分配物理页。此时进行物理页的分配;
通过函数get_pte可以获取出错的线性地址对应的虚拟页起始地址对应到的页表项,如果所需物理页没有被分配,则利用pgdie_alloc_page函数分配物理页。
d.修改lab3/kern/mm/vmm.c文件中的do_pgfault()函数,填充部分如下:
e.使用make qemu查看结果,显示”check_pgfault() succeeded!”,表示练习1基本正确。截图如下:
③ 请回答如下问题:
a.请描述页目录项(Page Director Entry)和页表(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
在lab3/kern/mm/mmu.h文件中,页表PTE的组成部分:
分页机制的实现确保了虚拟地址和物理地址之间的对应关系。
通过查找虚拟地址是否存在于一二级页表中,可知发现该地址是否是合法的,同时可以通过修改映射关系实现页替换操作;
在实现页替换时涉及到换入换出:换入时需要将某个虚拟地址对应的磁盘的一页内容读入到内存中,换出时需要将某个虚拟页的内容写到磁盘中的某个位置。而页表项可以记录该虚拟页在磁盘中的位置,为换入换出提供磁盘位置信息,页目录项则是用来索引对应的页表
PDE和PTE均保留了一些位给操作系统使用,具体可以应用在页替换算法时。present位为0时CPU不使用PTE上内容,这时候这些位便会闲置,可以将闲置位用于保存别的信息,例如页替换算法被换出的物理页在交换分区的位置等。同时,需要注意到dirty位,操作系统根据脏位可以判断是否对页数据进行write through。
b.如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做什么事情?
首先调用中断机制,引起中断;
将发生错误的线性地址保存在cr2寄存器中,并在中断栈中依次压入EFLAGS、CS、FIP,把访问异常码error code保存到中断栈;
根据中断描述符表查询到对应页访问异常的ISR,跳转到对应的ISR处执行,实现缺页服务例程。
3.练习2:补充完成基于FIFO的页面替换算法(需要编程)
完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_vistim函数。通过对swap的测试后,会有”check_swap() succeeded!”的输出,表示练习2基本正确。请在实验报告中简单说明你的设计实现过程。
① 设计实现过程
a.先进先出(FIFO)页替换算法基本思想:
当需要调入一页必须淘汰一个旧页时,总是淘汰最先调入内存的那一页,或者说在主存中驻留时间最长的那一页。该算法实现较为简单,只需要维护一个位于所有内存中的逻辑页面链表,链表元素按照驻留时间排序,链首最长,如果出现缺页时将链首页面进行置换,新页面加到链尾即可。但是该算法性能较差,可能会将经常调用的页面调出,可能导致Belady异常(页错误率可能会随着所分配的帧数的增加而增加)。
b.补全lab3/kern/mm/swap_fifo.c文件中的_fifo_map_swappable()函数,此函数用于记录页访问情况相关属性,维护FIFO的队列情况。实现如下:
c.补全lab3/kern/mm/swap_fifo.c文件中的_fifo_swap_out_vistim()函数,此函数将链表头的物理页面取出,再删掉对应的链表项。实现如下:
d.补全lab3/kern/mm/vmm.c文件中的do_pgfault()函数,填充部分如下:
e.使用make qemu查看结果,显示”check_swap() succeeded!”,表示练习2基本正确。截图如下:
② 如果要在ucore上实现”extended clock页替换算法”,请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案;如果不是,请给出你的新的扩展和基此扩展的设计方案。
答:
【clock页替换算法】:
最近最少使用(LRU)算法的一种近似实现。时钟页替换算法把各个页面组织成环形链表的形式,类似于一个钟的表面。然后把一个指针(简称当前指针)指向最老的那个页面,即最先进来的那个页面。另外,时钟算法需要在页表项(PTE)中设置了一位访问位来表示此页表项对应的页当前是否被访问过。当该页被访问时,CPU中的MMU硬件将把访问位置“1”。当操作系统需要淘汰页时,对当前指针指向的页所对应的页表项进行查询,如果访问位为“0”,则淘汰该页,如果该页被写过,则还要把它换出到硬盘上;如果访问位为“1”,则将该页表项的此位置“0”,继续访问下一个页。该算法近似地体现了LRU的思想,且易于实现,开销少,需要硬件支持来设置访问位。时钟页替换算法在本质上与FIFO算法是类似的,不同之处是在时钟页替换算法中跳过了访问位为1的页。
【实现方案】:
现有的swap_manager框架足以支持在ucore中实现此算法。在mmu.h中通过宏定义的方式实现了PTE组成,其中包括脏位和访问位。因此我们可以根据这两位知道虚拟页是否被访问过以及是否被改写过,再将FIFO的队列式链表改造成环形循环列表。
原来的初始化函数不变
维护链表的函数_fifo_map_swappable进行修改,令每次加入新节点时,不仅将其加在最后一个链表项后面,而且将其指针指向head,形成环形循环列表。
swap_out_victim()函数改变较多:总是从当前指针开始,对循环链表逐一扫描,通过判断每个链表项所指的物理页状态来决定进行何种操作:
(<access,dirty>表示物理页状态)
如果状态是(0, 0),说明当前数据无效且没有被修改,只需将该物理页面从链表上删去,该物理页面记为换出页面,且不需要将其写入swap分区;
如果状态是(1, 0), 将该物理页对应的虚拟页的PTE中的访问位都置成0,然后指针跳转到下一个物理页面;
如果状态是(0, 1),则将该物理页对应的虚拟页的PTE中的dirty位改成0,并且将该物理页写入到外存中,然后指针跳转到下一个物理页;
如果状态是(1, 1),则该物理页的所有对应虚拟页的PTE中的访问为置成0,然后指针跳转到下一个物理页面
③ 回答以下问题:
a.需要被换出的页的特征是什么?
答:
需要被换出的页的特征是:最早被换入,且最近没有再被访问的页。
b.在ucore中如何判断具有这样特征的页?
答:
通过判断页是否被访问过来判断,将未访问过的物理页进行FIFO操作即可。
c.何时进行换入和换出操作?
答:
当需要调用的页不在页表中,并且页表已满的情况下,会引发Page Fault,此时需要进行换入换出操作。当保存在磁盘中的内存需要被访问时,需要进行换入操作;当位于物理页框中的内存被页面特换算法选择时,需要进行换出操作。
四、实验结果
在lab3目录下完成练习1和练习2时,分别执行make qemu指令,得到结果如图:
- 练习1:显示”check_pgfault() succeeded!”,表示练习1基本正确。
- 练习2:显示”check_swap() succeeded!”,表示练习2基本正确。
五、实验心得
本次实验主要是关于页故障的处理,其中,我了解到了若干种换页的选择算法。
这次实验巩固了我对内存管理的理解,更重要的是提高了动手操作的能力,更加理解了出现页故障等页访问问题时,操作系统的工作。