实验目的
实验内容
练习介绍
本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现,结合磁盘提供的缓存空间,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。这个实验与实际操作系统中的实现比较起来要简单,不过需要了解实验一和实验二的具体实现。实际操作系统系统中的虚拟内存管理设计与实现是相当复杂的,涉及到与进程管理系统、文件系统等的交叉访问。如果大家有余力,可以尝试完成扩展练习,实现extended clock页替换算法。
虚存管理介绍
-
内存地址虚拟化能有效隔离不同进程的内存访问空间:在有了分页机制后,程序员或CPU“看到”的地址已经不是实际的物理地址了,这已经有一层虚拟化,我们可简称为内存地址虚拟化。有了内存地址虚拟化,我们就可以通过设置页表项来限定软件运行时的访问空间,确保软件运行不越界,完成内存访问保护的功能。
-
内存地址虚拟化能提供更大的内存“空间”:
- 按需分页:通过内存地址虚拟化,可以使得软件在没有访问某虚拟内存地址时不分配具体的物理内存,而只有在实际访问某虚拟内存地址时,操作系统再动态地分配物理内存,建立虚拟内存到物理内存的页映射关系,这种技术称为按需分页(demand paging) 。
- 页换入换出:把不经常访问的数据所占的内存空间临时写到硬盘上,这样可以腾出更多的空闲内存空间给经常访问的数据;当CPU访问到不经常访问的数据时,再把这些数据从硬盘读入到内存中,这种技术称为页换入换出(page swap in/out) 。这种内存管理技术给了程序员更大的内存“空间”,从而可以让更多的程序在内存中并发运行。
虚存管理总体框架
1.完成初始化虚拟内存管理机制:IDE 硬盘读写,缺页异常处理
2.设置虚拟页空间和物理页帧空间,表述不在物理内存中的“合法”虚拟页
3.完善建立页表映射、页访问异常处理操作等函数实现
4.执行访存测试,查看建立的页表项是否能够正确完成虚实地址映射
5.执行访存测试,查看是否正确描述了虚拟内存页在物理内存中还是在硬盘上
6.执行访存测试,查看是否能够正确把虚拟内存页在物理内存和硬盘之间进行传递
7.执行访存测试,查看是否正确实现了页面替换算法等
实验过程
练习0:填写已有实验
本实验依赖实验1/2。请把你做的实验1/2的代码填入本实验中代码中有“LAB1”,“LAB2”的注释相应部分。
用meld工具比较两个文件夹的差异
只需对kdebug.c、trap.c、default_pmm.c、pmm.c
四个文件进行相应补充即可
练习1:给未被映射的地址映射上物理页
完成do_pgfault(mm/vmm.c)函数,给未被映射的地址映射上物理页。设置访问权限 的时候需要参考页面所在 VMA 的权限,同时需要注意映射物理页时需要操作内存控制 结构所指定的页表,而不是内核的页表。注意:在LAB3 EXERCISE 1处填写代码。执行
1 | make qemu |
后,如果通过check_pgfault函数的测试后,会有“check_pgfault() succeeded!”的输出,表示练习1基本正确。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
- 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
- 如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
实验过程
查看init.c函数
可以看出虚拟内存初始化前便进行了物理内存初始化
查看vmm.h相关结构体内容:
1 | struct mm_struct { // 描述一个进程的虚拟地址空间 每个进程的 pcb 中 会有一个指针指向本结构体 |
其中mm_struct 描述了整个进程的虚拟地址空间,而 vma_struct 描述了进程中的一小部分虚拟内存空间
查看提示
根据提示填入代码
1 | ptep = get_pte(mm->pgdir, addr, 1); // 根据引发缺页异常的地址 去找到 地址所对应的 PTE 如果找不到 则创建一页表 |
make qemu
显示成功,但check_swap()错误,出现了缺页错误
回答问题
本练习问题和lab2的练习2完全一致
请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中每个组成部分的含义以及对ucore而言的潜在用处。
从低到高,分别是:
- P (Present) 位:表示该页保存在物理内存中。
- R (Read/Write) 位:表示该页可读可写。
- U (User) 位:表示该页可以被任何权限用户访问。
- W (Write Through) 位:表示 CPU 可以直写回内存。
- D (Cache Disable) 位:表示不需要被 CPU 缓存。
- A (Access) 位:表示该页被访问过。
- S (Size) 位:表示一个页 4MB 。
- 9-11 位保留给 OS 使用。
- 12-31 位指明 PTE 基质地址。
从低到高,分别是:
- 0-3 位同 PDE。
- C (Cache Disable) 位:同 PDE D 位。
- A (Access) 位:同 PDE 。
- D (Dirty) 位:表示该页被写过。
- G (Global) 位:表示在 CR3 寄存器更新时无需刷新 TLB 中关于该页的地址。
- 9-11 位保留给 OS 使用。
- 12-31 位指明物理页基址。
如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
使能页机制:
Cr0系列的控制寄存器:
寄存器名称 | 描述 |
---|---|
CR0 | 包含处理器标志控制位,如PE,PG,WP等 |
CR1 | 保留 |
CR2 | 专门用于保存缺页异常时的线性地址 |
CR3 | 保存进程页目录地址 |
CR4 | 扩展功能(如判断物理地址扩展模式等),Pentium系列(包括486的后期版本)处理器中才实现 |
如果出现了页访问异常,那么硬件将引发页访问异常的地址将被保存在cr2寄存器中,设置错误代码,然后触发Page Fault异常。
下图为页访问异常的各种情况:
页访问异常(Page Fault)
- 在程序的执行过程中由于某种原因(页框不存在/写只读页等) 而使 CPU 无法最终访问到相应的物理内存单元,即无法完成从虚拟地址到物理地址映射时,CPU 会产生一次页访问异常,从而需要进行相应的页访问异常的中断服务例程。这个页访问异常处理的时机被操作系统充分利用来完成虚存管理,即实现“按需调页”/“页换入换出”处理的执行时机。当相关处理完成后,页访问异常服务例程会返回到产生异常的指令处重新执行,使得应用软件可以继续正常运行下去。
- 具体而言,当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等) ,就会发生页访问异常。产生页访问异常的原因主要有:
- 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销)
- 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上)
- 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)
- CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。页访问异常错误码有32位。位0为1表示对应物理页不存在;位1为1表示写异常(比如写了只读页);位2为1表示访问权限异常(比如用户态程序访问内核空间的数据)
- CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中对应的中断服务例程可以检查CR2的内容,从而查出线性地址空间中的哪个页引起本次异常。
练习2:补充完成基于FIFO的页面替换算法
完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_victim函数。通过对swap的测试。注意:在LAB3 EXERCISE 2处填写代码。执行
1 | make qemu |
后,如果通过check_swap函数的测试后,会有“check_swap() succeeded!”的输出,表示练习2基本正确。
请在实验报告中简要说明你的设计实现过程。
请在实验报告中回答如下问题:
- 如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题
- 需要被换出的页的特征是什么?
- 在ucore中如何判断具有这样特征的页?
- 何时进行换入和换出操作?
实验过程
查看vmm.c中lab2注释
根据注释写代码
1 | else { // 如果 PTE 存在 说明此时 P 位为 0 该页被换出到外存中 需要将其换入内存 |
查看swap_fifo.c注释
根据注释写代码
此时完成的是 FIFO 置换算法,因此每次换出的都应该是最先进来的 页
1 | static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) { |
重新make qemu,得到下面的结果
make grade
成功运行
观察FIFO的check函数的对应关系
1 | swap.c |
1 | swap_fifo.c |
回答问题
如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题
- 需要被换出的页的特征是什么?
- 在ucore中如何判断具有这样特征的页?
- 何时进行换入和换出操作?
支持。
在mmu.h文件可以查看页表项标志位
其中的PTE_A和PTE_D可用于实现extended clock页替换算法,可对swap_fifo.c做相应修改即可。
-
需要被换出的页的特征是什么?
页表项的修改位 D(Dirty Bit)为 0 的页 且访问位 A( Access Bit) 为 0 的页。
-
在 ucore 中如何判断具有这样特征的页?
检查页表项的访问位和修改位是不是1。当内存页被访问后,MMU 将在对应的页表项的
PTE_A
这一位设为1,当内存页被修改后,MMU 将在对应的页表项的PTE_D
这一位设为1。 -
何时进行换入和换出操作?
当进程访问的物理页没有在内存中缓存而是保存在磁盘中(缺页且内容在磁盘中)时,需要进行换入操作; 当位于物理页中的内存由于物理页帧满被页面替换算法选择换出(
alloc_page
返回NULL)时,需要进行换出操作。
扩展练习 Challenge 1:实现识别dirty bit的 extended clock页替换算法
实验过程
实现基础:
复制swap_fifo.c和swap_fifo.h到同原文件夹并重命名为swap_clock.c和swap_clock.h
对照_fifo_swap_out_victim函数结构
1 | static int _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) { |
依据算法写出代码
1 | !(*ptep & PTE_A) && !(*ptep & PTE_D) //没被访问过 也没被修改过 |
根据算法得出页置换过程
将其应用于swap_out_victim的修改
注意:对于每次类似于*(unsigned char *)0x1000 = 0x0a;
的赋值过程,对应页面的Dirty位和Access位均置1
swap_extended_clock.c修改代码,主要是对struct swap_manager,swap_out_victim函数,check_swap函数进行修改,并对各函数进行更名
1 |
|
swap_clock.h
1 |
|
为了使用extended clock算法,对swap.c进行修改
1 | - #include <swap_fifo.h> |
重新 make qemu
成功运行
swap_check()函数通过
另外,此实验的页面访问权限为内核态,对于lab1的challenge 2,如果按3切换到用户态后会出现缺页中断,因为用户态不能访问只能内核态可以访问的页表,即无法访问内存。用户态没有权限访问虚拟地址对应的物理页面;该虚拟地址也不在合法的虚拟地址空间里,由于只设置了很小的合法虚拟地址空间。在内核态下,由于这部分虚拟地址对应的是操作系统内核代码,在启动虚拟内存管理之前就,已经装入了内存,建立好页的映射关系,是不会发生缺页中断的,不会被换出的。
实验总结
本次实验是关于虚拟内存管理,主要有Page Fault异常处理页替换算法的相关实验,了解了相关操作。本次实验题目量较少,需要查找分析代码里宏定义和函数定义来完成对已有代码的理解和要完成代码的使用。通过本次实验我深入理解了虚拟内存管理机制,对课程内容有了更深的理解。