Part 2: Virtual Memory
在做实验的第二部分之前,课程建议我们仔细阅读Intel 80386 Reference Manual的5.2和6.4小结,熟悉页转换和页保护的相关知识,并且基本了解分段的相关知识。
一.分页相关知识:
1.仅当寄存器CR0的PG位被设置时,开启分页机制,该位由操作系统在软件初始化时设置
2.页帧:4K大小的地址连续的物理内存
3.线性地址格式:
DIR为页目录表索引,PAGE为页表索引,OFFSET为页内偏移量
4.线性地址转物理地址过程
启用分页前,页目录所在的物理页面的首地址需要存放到 CR3 寄存器中,这样x86处理器在进行页式地址转换时会自动地从CR3中取得页目录物理地址,然后根据10位的DIR字段在页目录表中找到对应的页目录表项,页目录表项格式如下图:
页目录表项的高20位将指出对应的页表所在物理页的首地址,再根据10位的PAGE字段在页表中找到对应的页表项,页表项格式如下图:
页表项的高二十位指出线性地址对应的物理页起始地址,二十位的物理页起始地址与线性地址的低十二位OFFSET字段拼接就得到了32位的物理地址。
各位含义:
PRESENT位:p位为0时,页表项格式如5-11所示,此时该表项不可用于地址转换,当使用虚拟地址访问到该表项时会引发缺页中断。
ACCESSED位:访问位,在对页表项对应的物理页进行读写之前,处理器会将页目录表项和页表项中的访问位都设为1。
DIRTY位:脏位,用于标志页表项对应的物理页中的内容是否被修改,在对物理页进行写入之前被置为1。
R/W和U/S位:这两个位用于页面级保护
另外为了提高地址转换效率,处理器会将常用的页表项存储在高速缓存中,地址转换前,先查找高速缓存中的快表,找不到时再去查找内存中的页表。每当修改页表时,OS程序员都需要刷新高速缓存,一般有两种方法:
1.使用MOV指令重载CR3寄存器:
例如 MOV CR3, EAX
2.通过执行任务切换到具有与当前TSS的CR3不同的TSS。
二.页面级保护
主要有两种保护形式:
1.限制可寻址范围
U/S位:用来定义页面的访问者应该具备的权限。为1是用户权限即可访问,为 0表示需要高特权级才能
访问。
U/S位和CPL相关(Current Privilege Level,存在于CS寄存器的低两位,用于描述当前进程的权限级别),当
CPL为0,1,2时,处理器处于特权等级,能访问所有页,当CPL为3时,处理器只能访问用户权限可访问的页。
2.类型检查
R/W=0时,只读权限,R/W=1时,可读可写,当处理器处于特权等级时,所有页面可读可写,处于用户级时,其读写权限依赖于R/W位,且要求特权等级才可访问的页都不能被读或写。
三.段相关知识
x86术语中,虚拟地址由段选择器和段内偏移量组成,线性地址是段转换后,页转换前的中间产物,物理地址是段页转换后的最终产物。
JOS中由于GDT表已经将段转换禁用(通过将段基址设为0,段范围设知道0xfffffff),因此段选择器实际不起作用,虚拟地址的offset字段就等价于线性地址。在lab1的part3中,我们已经设置了一个简单的页表使得虚拟地址0xf0100000能够映射到0x00100000,接下来我们将根据memlayout.h中的虚拟空间布局对JOS进行设置,我们将对这个简单的页表进行扩展以映射从虚拟地址0xf0000000开始的前256MB物理内存,和该虚拟地址空间的许多其他区域。
四.uintptr_t和physaddr_t
一旦进入保护模式,所有对内存的引用(如指针)都会被解释为虚拟地址,并由MMU转换,JOS源代码为了规范性,区分了两种引用地址的情况,uintptr_t表示不透明的虚拟地址,physaddr_t表示物理地址。实际上它们都是32位整型uint32_t的同义词,JOS内核可以通过首先将uintptr_t转换为指针类型来解引用。相比之下,内核无法明智地对物理地址解引用,因为MMU会转换所有内存引用。如果将physaddr_t强制转换为指针并解引用,那么硬件会将其解释为虚拟地址,然后加载,可能无法获得预期的结果。
问题:
答:显然变量x的类型为uintptr_t,因为对value进行了赋值,上面提到对物理地址解引用是无意义的,那么
value是一个虚拟地址,如果x是physaddr_t,那么对一个物理地址赋值一个虚拟地址也是无意义的。
五.引用计数
page_info的pp_ref变量代表对该结构体对应物理页的引用计数,当计数为0时,该页面可以被释放,使用
page_alloc时,注意它返回页面的pp_ref始终为0,如果要对它返回的页面进行某些操作(如插入到页表中),那么pp_ref应当递增,有时其他函数(如page_insert)会进行处理,有时调用page_alloc的函数必须直接执行此操作。
六.页表管理
编写一组管理页表的例程
在kern/pmap.c中,补充下列函数
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()
补充完毕后,进行测试,确保check_page能够顺利通过.
pgdir_walk():
boot_map_region():
page_lookup():
page_remove():
page_insert():
这里有个坑,注意图片左上方红色标注区域,插入失败时,要返回-E_NO_MEM,而非E_NO_MEM。
测试一下,成功通过啦: