实验目的
实验内容
本次实验包含三个部分。首先了解如何发现系统中的物理内存;然后了解如何建立对物理内存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。本实验里面实现的内存管理还是非常基本的,并没有涉及到对实际机器的优化,比如针对 cache 的优化等。如果大家有余力,尝试完成扩展练习。
练习介绍
为了实现lab2的目标,lab2提供了3个基本练习和2个扩展练习,要求完成实验报告。
对实验报告的要求:
- 基于markdown格式来完成,以文本方式为主
- 填写各个基本练习中要求完成的报告内容
- 完成实验后,请分析ucore_lab中提供的参考答案,并请在实验报告中说明你的实现与参考答案的区别
- 列出你认为本实验中重要的知识点,以及与对应的OS原理中的知识点,并简要说明你对二者的含义,关系,差异等方面的理解(也可能出现实验中的知识点没有对应的原理知识点)
- 列出你认为OS原理中很重要,但在实验中没有对应上的知识点
查看本次实验所有需要填写代码的地方
1 | [~/Desktop/ucore_lab/labcodes/lab2_new] |
练习0:填写已有实验
本实验依赖实验1。请把你做的实验1的代码填入本实验中代码中有“LAB1”的注释相应部分。提示:可采用diff和patch工具进行半自动的合并(merge),也可用一些图形化的比较/merge工具来手动合并,比如meld,eclipse中的diff/merge工具,understand中的diff/merge工具等。
命令行键入meld
设置lab1,lab2两个文件夹
比较合并
其实只要将lab1的 kern/debug/kdebug.c , kern/init/init.c, kern/trap/trap.c复制到lab2即可
练习1:实现 first-fit 连续物理内存分配算法
在实现first fit 内存分配算法的回收函数时,要考虑地址连续的空闲块之间的合并操作。提示:在建立空闲页块链表时,需要按照空闲页块起始地址来排序,形成一个有序的链表。可能会修改default_pmm.c中的default_init,default_init_memmap,default_alloc_pages, default_free_pages等相关函数。请仔细查看和理解default_pmm.c中的注释。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
- 你的first fit算法是否有进一步的改进空间
先直接运行,出现如下错误
错误是在
1 | assert((p0 = alloc_page()) == p2 - 1); |
最先匹配算法
观察default_pmm.c文件头
1 |
查看./pmm.h
1 |
|
pmm_manager是内存分配结构体
包含6个函数指针(的声明)
包含初始化函数init,分配函数alloc_pages,释放函数free_pages,检查函数check。
其定义在./default_pmm.c的末尾
1 | const struct pmm_manager default_pmm_manager = { |
因此可知在default_pmm.c中要完成对连续内存分配中使用到的各个函数的定义。
引用了库文件memlayout.h
查看./memlayout.h
1 | /* * |
页面page定义
- ref
表示这样页被页表的引用记数,应该就是映射此物理页的虚拟页个数。一旦某页表中有一个页表项设置了虚拟页到这个Page管理的物理页的映射关系,就会把Page的ref加一。反之,若是解除,那就减一。 - flags
表示此物理页的状态标记,有两个标志位,第一个表示是否被保留,如果被保留了则设为1(比如内核代码占用的空间)。第二个表示此页是否是free的。如果设置为1,表示这页是free的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。 - property
用来记录某连续内存空闲块的大小,这里需要注意的是用到此成员变量的这个Page一定是连续内存块的开始地址(第一页的地址)。 - page_link
是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块,page_link所用的空间实际就是页面空间
而页面实际大小为结构体所占的空间大小
有7个宏定义函数
- SetPageReserved(page) 设置PG_reserved=1,标识的内存是被内核使用的,不能释放
- SetPageProperty(page) 设置PG_property=1,表示的可分配的意思,0则表示已被使用
- le2page(le, member) 获得以le为地址起点,member为数据(结构)类型所占用字节的的变量。一个地址可能有不同大小的数据类型存在,一个结构体及其内部第一个成员及其嵌套成员的起始地址是相同的
查看libs/list.h
包含以下自定义数据结构链表和链表操作函数
1 | struct list_entry { |
练习1实现代码
主要修改代码如下
1 | #define free_list (free_area.free_list) |
运行结果
可以看到分配函数alloc_page()测试成功,且出现了新的错误,需要等到练习2解决
Q:你的first fit算法是否有进一步的改进空间
可以看出,page_link数据结构用的是线性结构链表,程序运行时间主要花在查找方面(循环查找符合条件的节点),算法时间复杂度为,改进方面可以使用非线性结构(例如线段树)来存储页面空间,提高查找效率,使用二分查找的话可以使时间复杂度降至
练习2:实现寻找虚拟地址对应的页表项
通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项环节中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。本练习需要补全get_pte函数 in kern/mm/pmm.c,实现其功能。请仔细查看和理解get_pte函数中的注释。get_pte函数的调用关系图如下所示:
图1 get_pte函数的调用关系图
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
- 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中每个组成部分的含义以及对ucore而言的潜在用处。
- 如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
段页式机制
名称 | 描述 |
---|---|
页目录索引表(PDT) | 一级索引 |
页表索引表(PTT) | 二级索引 |
页表项(PTE) | 页表项 PDT(1024项PDE),PTT(1024项PTE) |
-
虚拟地址映射关系:
1
虚拟 = 物理 + 0xC0000000
pmm.h中有下列的宏定义函数
1 | //分配1页空间 |
1 |
get_pte函数
1 | //get_pte - get pte and return the kernel virtual address of this pte for la |
pgdir: PDT(页目录索引表)的内核虚地址
la: 需要映射的线性地址
create: 一个决定是否要分配一个页面给PT(页表)的逻辑值
按照注释提示在#endif后添加如下代码
1 | pde_t *pdep = &pgdir[PDX(la)]; // (1)获取页目录项,*pdep是la所在的二级页表的物理地址,PDX(la)一级页目录表的下标,此运算其实是pgdir基址+PDX(la)偏移量,由在一级页目录表中查找得到二级页表的地址 |
make clean后重新make clean,得到如下结果
可以发现get_pte的错误消失,显示了新的错误,需在练习3解决
请描述页目录项(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异常。
练习3:释放某虚地址所在的页并取消对应二级页表项的映射
当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得此物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除。请仔细查看和理解page_remove_pte函数中的注释。为此,需要补全在 kern/mm/pmm.c中的page_remove_pte函数。page_remove_pte函数的调用关系图如下所示:
图2 page_remove_pte函数的调用关系图
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
- 数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?
- 如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事? 鼓励通过编程来具体完成这个问题
1 | //page_remove_pte - free an Page sturct which is related linear address la |
pmm.h部分函数定义
1 | typedef uintptr_t pte_t; |
按照注释在#endif后添加如下代码:
1 | if ((*ptep & PTE_P)) {//判断页表入口是否存在,P (Present) 位:表示该页保存在物理内存中。 |
make clean 后重新make qemu得到如下结果
另外,对于lab1的challenge1的代码,似乎不能在lab2正常运行
当键入0时,会出现缺页异常
遂将其注释便运行正常
数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?
有。
查看pmm.h PDE, PTE与page之间的转化
1 | static inline ppn_t |
VPN: 虚拟分页地址 virtual page number
PPN: 物理分页地址 physical page number
页目录项或页表项的前20位表示它对应的是哪个Page。
如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事?
虚地址和物理地址之间有一个偏移,即存在映射关系:
1 | phy addr + KERNBASE = virtual addr |
KERNBASE为虚拟地址空间中的内核基址,即偏移量。
查看lab2/kern/mm/memlayout.h,得知其定义的KERNBASE
是:
1 |
将其改为0x0
即可使得虚拟地址与物理地址相等
通过ld工具形成的ucore的起始虚拟地址从0xC0100000开始,而bootloader把ucore放在了起始物理地址为0x100000的物理内存空间。要使得虚拟地址与物理地址相等:
tools/kernel.ld
1 | SECTIONS { |
修改链接脚本,将内核起始虚拟地址0xC0100000
修改为0x100000
实验小结
本次实验是关于物理内存管理,主要有物理内存分配和建立页表的相关实验,了解了如何对物理内存进行初步管理和对页表的相关操作。所有的编程内容涉及到的代码量繁多,需要查找分析代码里宏定义和函数定义来完成对已有代码的理解和要完成代码的使用。通过本次实验我深入理解了段页式内存管理机制,对课程内容有了更深的理解。