MIT JOS学习笔记03:kernel 02(2016.11.08)
未经许可谢绝以任何形式对本文内容进行转载!
本篇接着上一篇对kernel的分析。
(5)pte_t * pgdir_walk(pde_t *pgdir, const void *va, int create)
这个函数是整个JOS操作系统页式内存管理最重要的函数。在这个函数中,JOS的设计者要求我们对于给定的一个页目录“pgdir”,返回线性地址(这是虚拟地址)“va”对应的页表入口地址。先用MIT自己的一张图来解释下整个地址转换的过程:
在本函数中,返回值就是上图Page Table中的<PPN Flags>,整个过程显然需要两次查表,一次是查Page Directory,另一次是查Page Table。但是有一个问题是,虚拟地址“va”对应的页目录项可能为空(NULL),即对应的二级页表(Page Table)尚未创建,这时我们就要根据create参数来决定是否分配一张二级页表。这里有个必须强调的细节:无论是页目录(Page Directory,即一级页表)还是页表(Page Table,即二级页表),保存的都是物理地址而不是虚拟地址。因为整个地址的转换实际上是由qemu模拟的mmu(Memory Management Unit,内存管理单元)硬件实现的,如果保存的是虚拟地址,就增加了mmu查表的次数,显然会影响kernel的速度。但是,kernel访问表和表项时又只能通过虚拟地址,这时我们可以用“kern/pmap.h”中的另一个函数page2kva()。
具体实现的代码如下:
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in //test output
//cprintf(">> pgdir_walk() was called!\n"); pte_t *result;
if (pgdir[PDX(va)] == (pte_t)NULL) { //yet to create
if (create == )
return NULL;
else {
struct Page *tmp = page_alloc(ALLOC_ZERO);
if (tmp == NULL)
return NULL; //failed to alloc
else {
tmp->pp_ref++;
pgdir[PDX(va)] = page2pa(tmp) | PTE_P | PTE_W |PTE_U; //save the physical address of newly allocated page in page dir
result = page2kva(tmp); //translate into an virtual address for kernel use
}
}
}
else
result = page2kva(pa2page(PTE_ADDR(pgdir[PDX(va)]))); return &result[PTX(va)];
}
pte_t * pgdir_walk(pde_t *pgdir, const void *va, int create)
(6)struct Page * page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
我们先暂时跳过page_insert()函数,因为page_insert()需要调用到page_lookup()和page_remove()。在本函数中,JOS的设计者要求我们对于给定的一个页目录“pgdir”,返回虚拟地址“va”映射到的物理页框对应的Page结构。我们可以通过pgdir_walk()函数来实现。
在pgdir_walk()中,我们可以得到Page Table中的<PPN Flags>,其中高20位的PPN就对应着一个物理页框。所以我们可以调用pgdir_walk()查找虚拟地址“va”对应的页表入口地址,如果返回为NULL,说明“va”尚未映射到任何一个物理页框上,也就没有对应的Page结构,如果返回不为NULL,我们只需要用PTE_ADDR()宏抹掉低12位的标志位,然后用pa2page()函数就能得到映射到的物理页框对应的Page结构。在这个过程中,如果参数pte_store不为NULL,则把pgdir_walk()得到的页表入口地址用pte_store传递回给调用者(注意pte_store是指向pte_t *的指针)。
具体实现的代码如下:
struct Page *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in //test output
//cprintf(">> page_lookup() was called!\n"); pte_t *pte = pgdir_walk(pgdir, va, );
if (pte == NULL)
return NULL; if (pte_store != )
*pte_store = pte; return pa2page(PTE_ADDR(pte[]));
}
struct Page * page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
(7)void page_remove(pde_t *pgdir, void *va)
page_remove()函数主要是用来断开虚拟地址“va”当前在页目录“pgdir”中的映射。要实现这一功能,我们需要用page_lookup()查找是否存在“va”对应的Page结构,如果不存在,我们需要用0抹掉该页表中对应的表项(尽管不存在对应的表项,但是还是抹掉以防万一),然后使“va”对应的TLB(Translation Lookaside Buffer,现在一般翻译为“快表”)失效;如果存在,我们需要尝试调用page_decref()减少该Page结构的pp_ref(即页面引用次数),这一过程中如果页面引用次数降为0,则由page_decref()释放该页面(放回page_free_list链表中),之后还需要用0抹掉该页表中对应的表项,最后同样地也需要让TLB失效。
具体实现的代码如下:
void
page_remove(pde_t *pgdir, void *va)
{
// Fill this function in //test output
//cprintf(">> page_remove() was called!\n"); pte_t *pte;
struct Page *page = page_lookup(pgdir, va, &pte); if (page != NULL)
page_decref(page); pte[] = ;
tlb_invalidate(pgdir, va);
}
void page_remove(pde_t *pgdir, void *va)
(8)int page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)
现在我们可以回到page_insert()函数上来了。JOS的设计者要求我们对于给定的页目录“pgdir”,把虚拟地址“va”映射到给定的物理页框对应的Page结构“pp”上,同时设置低12位的标志位为“perm”。
为了实现这一功能,我们需要用page_lookup()查找“va”在“pgdir”中是否已经映射到了某一个Page结构上,如果否,说明可以直接建立“va”到“pp”的映射;如果是,就有两种可能:
1. “va”已经映射到了“pp”上
2. “va”映射到了“pp”以外的Page结构上
对于第一种情况,我们只需要修改低12位的标志位即可。对于第二种情况,我们需要调用page_remove()断开原有的映射,然后再建立新的映射。注意,所谓的建立映射,实质上就是修改Page Table中对应的<PPN Flags>项,该项可以用pgdir_walk()得到。
具体实现的代码如下:
int
page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)
{
// Fill this function in //test output
//cprintf(">> page_insert() was called!\n"); struct Page *page = page_lookup(pgdir, va, NULL);
pte_t *pte; if (page == pp) { //re-insert into the same place
pte = pgdir_walk(pgdir, va, );
pte[] = page2pa(pp) | perm | PTE_P;
return ;
} if (page != NULL) //remove original page if existed
page_remove(pgdir, va); pte = pgdir_walk(pgdir, va, );
if (pte == NULL)
return -E_NO_MEM; pte[] = page2pa(pp) | perm | PTE_P;
pp->pp_ref++; return ;
}
int page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)
到此mem_init()中所有需要我们实现的函数都完成了,我们继续分析mem_init()的功能。
我们之前分析过page_init()的功能,在mem_init()中调用了这个函数初始化了page_free_list链表。随后mem_init()调用了下面三个检查我们的实现是否正确的函数:
check_page_free_list();
check_page_alloc();
check_page();
如果实现正确,在make qemu的时候console会显示“check_page_alloc() succeeded!”和“check_page() succeeded!”两行提示。
在这之后,JOS的设计者要求我们建立共计三块的物理页框对应的Page结构到虚拟地址的映射。
第一部分,建立pages[]数组所在的物理页框到虚拟地址UPAGES的映射,且标志位设置为“内核只读,用户只读”(PTE_U)。具体实现的代码如下:
for (i = ; i < ROUNDUP(npages*sizeof(struct Page), PGSIZE); i += PGSIZE)
page_insert(kern_pgdir, pa2page(PADDR(pages) + i), (void *)(UPAGES + i), PTE_U);
第二部分,建立bootstack所在的物理页框到虚拟地址KSTACKTOP的映射,且标志位设置为“内核可读可写,用户无权限”(PTE_W)。具体实现的代码如下:
for (i = ; i < KSTKSIZE; i += PGSIZE)
page_insert(kern_pgdir, pa2page(PADDR(bootstack) + i), (void *)(KSTACKTOP-KSTKSIZE + i), PTE_W);
第三部分,建立物理地址[0, 2^32 - KERNBASE)所在的物理页框到虚拟地址[KERNBASE, 2^32)的映射,且标志位设置为“内核可读可写,用户无权限”(PTE_W)。具体实现的代码如下:
for (i = ; i < 0xFFFFFFFF - KERNBASE; i += PGSIZE) {
page_insert(kern_pgdir, pa2page(i % (npages*PGSIZE)), (void *)(KERNBASE + i), PTE_W);
pa2page(i % (npages*PGSIZE))->pp_ref--; //this statement is to keep pp_ref == 0 in page_free_list
}
这里需要解释一下上面代码的第三行。为什么我们要对pp_ref进行自减操作?在后面JOS会调用check_kern_pgdir()和check_page_installed_pgdir()检查mem_init()函数实现是否正确,其中,check_page_installed_pgdir()里会模拟物理页框对应的Page结构的分配。这里就会出现一个问题:我们在第三部分的映射中已经修改了所有Page结构的引用次数pp_ref,而check_page_installed_pgdir()中的assert()断言默认所有Page结构一开始都是没有被引用的(即pp_ref == 0),所以我们需要额外去修改pp_ref(个人认为JOS的设计者在实现check_page_installed_pgdir()的时候没有考虑好)。
到此整个mem_init()的功能,或者说,页式虚拟内存管理就完全实现了。下一篇会开始介绍console和monitor。
MIT JOS学习笔记03:kernel 02(2016.11.08)的更多相关文章
- MIT JOS学习笔记02:kernel 01(2016.10.28)
未经许可谢绝以任何形式对本文内容进行转载! 在文章开头不得不说的是,因为这部分的代码需要仔细理清的东西太多,所以导致这篇分析显得很啰嗦,还请谅解. 我们在上一篇文章已经分析了Boot Loader的功 ...
- MIT JOS学习笔记01:环境配置、Boot Loader(2016.10.22)
未经许可谢绝以任何形式对本文内容进行转载! 一.环境配置 关于MIT课程中使用的JOS的配置教程网上已经有很多了,在这里就不做介绍,个人使用的是Ubuntu 16.04 + qemu.另注,本文章中贴 ...
- MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap
Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的: ...
- 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试
机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...
- OpenCV 学习笔记03 边界框、最小矩形区域和最小闭圆的轮廓
本节代码使用的opencv-python 4.0.1,numpy 1.15.4 + mkl 使用图片为 Mjolnir_Round_Car_Magnet_300x300.jpg 代码如下: impor ...
- OpenCV 学习笔记03 findContours函数
opencv-python 4.0.1 1 函数释义 词义:发现轮廓! 从二进制图像中查找轮廓(Finds contours in a binary image):轮廓是形状分析和物体检测和识别的 ...
- C++ GUI Qt4学习笔记03
C++ GUI Qt4学习笔记03 qtc++spreadsheet文档工具resources 本章介绍创建Spreadsheet应用程序的主窗口 1.子类化QMainWindow 通过子类化QM ...
- SaToken学习笔记-03
SaToken学习笔记-03 如果排版有问题,请点击:传送门 核心思想 所谓权限验证,验证的核心就是一个账号是否拥有一个权限码 有,就让你通过.没有?那么禁止访问! 再往底了说,就是每个账号都会拥有一 ...
- Redis:学习笔记-03
Redis:学习笔记-03 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 7. Redis配置文件 启动 ...
随机推荐
- 关于css样式2
css文本 CSS 文本属性可定义文本的外观. 通过文本属性,您可以改变文本的颜色.字符间距,对齐文本,装饰文本,对文本进行缩进,等等. 缩进文本 把 Web 页面上的段落的第一行缩进,这是一种最常用 ...
- X3850M2安装CertOS 7 KVM
在旧的X3850中安装linux系统,研究KVM. 通过选择fedora和centos测试,最终选择centos7来安装. 先安装了一台,安装第二台时又出现第一台的问题,决定记录下来防止记忆出错. 1 ...
- Python基础知识之认识字符串
Python有一个名为“STR”与许多方便的功能(有一个名为“串”,你不应该使用旧的模块),内置的字符串类. 字符串常量可以通过双或单引号括起来,尽管单引号更常用. 反斜杠工作单,双引号内的文字通常的 ...
- 北大poj-1005
I Think I Need a Houseboat Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 99271 Acce ...
- Linux内核分析——理解进程调度时机跟踪分析进程调度与进程切换的过程
20135125陈智威 +原创作品转载请注明出处 +<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验 ...
- 从angularJS改道Vue.js,趟过第一个坑!
vue采用 new vue()初始化,显然vue内部没有类似jquery ready函数的机制,在文档加载完成后再执行初始化. 今天新学习vue,由于vue采用es5的特殊机制更新UI,我不确定ipa ...
- uboot(二): Uboot-arm-start.s分析
声明:该贴是通过参考其他人的帖子整理出来,从中我加深了对uboot的理解,我知道对其他人一定也是有很大的帮助,不敢私藏,如果里面的注释有什么错误请给我回复,我再加以修改.有些部分可能还没解释清楚,如果 ...
- UCanCode发布升级E-Form++可视化源码组件库2015全新版 (V23.01)!
2015年4月. 成都 UCanCode发布升级E-Form++可视化源码组件库2015全新版 (V23.01)! --- 全面性能提升,UCanCode有史以来最强大的版本发布! E-Form++可 ...
- i2c总线,设备,驱动之间的关系
------ 总线上先添加好所有具体驱动,i2c.c遍历i2c_boardinfo链表,依次建立i2c_client, 并对每一个i2c_client与所有这个线上的驱动匹配,匹配上,就调用这个驱动的 ...
- Cannot forward after response has been committed
项目:蒙文词语检索 日期:2016-05-01 提示:Cannot forward after response has been committed 出处:request.getRequestDis ...