Lab2 report
练习0:填写已有实验
手动合并:修改lab2\kern\debug\kdebug.c和lab2\kern\trap\trap.c两个文件即可。
练习1:实现 first-fit 连续物理内存分配算法
实验思路
物理内存页管理器顺着双向链表进行搜索空闲内存区域,直到找到一个足够大的空闲区域,这是一种速度很快的算法,因为它尽可能少地搜索链表。如果空闲区域的大小和申请分配的大小正好一样,则把这个空闲区域分配出去,成功返回;否则将该空闲区分为两部分,一部分区域与申请分配的大小相等,把它分配出去,剩下的一部分区域形成新的空闲区。其释放内存的设计思路很简单,只需把这块区域重新放回双向链表中即可。
实验过程
default_init_memmap()函数:
这个函数是用来初始化空闲页链表的,主要有两个步骤:
a. 初始化每一个空闲页;
b. 计算空闲页的总数。
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));//检查是否为保留页
p->flags = p->property = 0;//设置标记位
SetPageProperty(p);
set_page_ref(p, 0);//清零引用计数
list_add_before(&free_list, &(p->page_link));//由于地址是从低地址向高地址增长,故采用头插法插入
}
base->property = n;
nr_free += n;//计算空闲页数
}
default_alloc_pages()函数:
这个函数是用来分配空闲页的,具体步骤如下:
a. 寻找足够大的空闲块,如果找到了,重新设置标志位,从空闲链表中删除此页;
b. 判断空闲块大小是否合适,如果不合适,分割页块,如果合适则不进行操作;
c. 计算剩余空闲页个数;
d. 返回分配的页块地址。
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
//在空闲页链表中寻找合适大小的页块
while ((le = list_next(le)) != &free_list) {
struct Page *p = le2page(le, page_link);
//找到!
if (p->property >= n) {
page = p;
int i;
struct Page *pp;
list_entry_t *temp_le;
temp_le = le;
for(i=0; iproperty > n) {
struct Page *p = page + n;
p->property = page->property - n;
}
nr_free -= n;
}
return page;
}
default_free_pages()函数:
这个函数的作用是释放已经使用完的页,把他们合并到freelist中,具体步骤如下:
a. 在freelist中查找合适的位置以供插入
b. 改变被释放页的标志位,以及头部的计数器
c. 尝试在freelist中向高地址或低地址合并
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
assert(PageReserved(base));//检查标志位是否错误
struct Page *p = base;
list_entry_t *le = &free_list;
//查找插入位置
while((le=list_next(le))!= &free_list){
p = le2page(le, page_link);
if(p>base){
break;
}
}
//向前插入n个页,改变标志位
for (p=base; p < base + n; p ++) {
list_add_before(le, &(p->page_link));
p->flags = 0;
set_page_ref(p, 0);
ClearPageReserved(p);
SetPageProperty(p);
}
//记录在头部
base->property = n;
//向高地址合并
p = le2page(le, page_link);
if (base + n == p) {
base->property += p->property;
p->property = 0;
}
//向高地址合并
le = list_prev(&(base->page_link));
p = le2page(le, page_link);
if(le!=&free_list && p==base-1){
while(le!=&free_list){
if(p->property){
p->property+=base->property;
base->property = 0;
break;
}
le=list_prev(le);
p=le2page(le,page_link);
}
}
nr_free += n;
}
改进的地方
也许可以改进查找算法使得查找速度更快。
练习2:实现寻找虚拟地址对应的页表项
实验思路
尝试获取页表的地址,如果获取不到就新建一个页表。
实现过程
get_pte()函数:
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
pde_t *pdep = &pgdir[PDX(la)];
//获取页表的地址不成功
if(!(*pdep & PTE_P)){
struct Page *page;
//申请一页
if(!create || (page = alloc_page())==NULL){
return NULL;
}
set_page_ref(page, 1); //引用加1
uintptr_t pa = page2pa(page);//获取地址
memset(KADDR(pa),0,PGSIZE);
*pdep = pa| PTE_U| PTE_W| PTE_P;//设置权限
}
//返回页表的地址
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
}
思考题
请描述页目录项(Pag Director Entry)和页表(Page Table Entry)中每个组成部分的含义和以及对ucore而言的潜在用处。
页目录项PDE=(页表起始物理地址&(~0x0FFF)) | PTE_U | PTE_W | PTE_P。
PDE的前20位代表了页表起始地址,PTE_U表示用户态软件可读物理内存页内容,PTE_W表示物理内存页可写,PTE_P表示物理内存页存在。
潜在用处:作为一个双向链表存储了目前所有的页的物理地址和逻辑地址的对应,替换算法中被换出的页从中选出。
页表项PTE=(pa&(~0x0FFF)) | PTE_P | PTE_W
PTE的前20位代表了该页对应的物理地址的前20位(由前20位就可以定位到对应的页了),PTE_P与PTE_W的含义与PDE中的对应位相同
PTE的索引是线性地址中的高11~20位。
潜在用处:页表(Page Table Entry)存储了替换算法中被换入的页的信息,替换后会将其映射到一物理地址。
如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
硬件首先接受操作系统给出的中断,然后进行换页的操作。即产生页访问异常后,CPU把引起页访问异常的线性地址装到寄存器CR2中,并给出了出错码errorCode,ucoreOS会把这个值保存在struct trapframe 中tf_err成员变量中。而中断服务例程会调用页访问异常处理函数do_pgfault进行具体处理。
练习3:释放某虚地址所在的页并取消对应二级页表项的映射
实验思路
判断此页被引用的次数,如果仅仅被引用一次,则这个页也可 以被释放。否则,只能释放页表入口。
实现过程
page_remove_pte()函数:
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
//判断页表入口是否存在
if (*ptep & PTE_P){
struct Page* page = pte2page(*ptep);
if (page_ref_dec(page)==0){
free_page(page);//仅引用1次则释放
}
*ptep = 0;
tlb_invalidate(pgdir, la);//引用多次则释放pte
}
}
思考题
数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?
有。页目录项或页表项的前20位表示它对应的是哪个Page。
如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事? 鼓励通过编程来具体完成这个问题。
由附录C.链接地址/加载地址/虚地址/物理地址 可知:
虚地址和物理地址之间有一个偏移,即存在映射关系:
phy addr + KERNBASE = virtual addr
KERNBASE为虚拟地址空间中的内核基址,即偏移量。
查看lab2/kern/mm/memlayout.h,得知其定义的KERNBASE是:
#define KERNBASE 0xC0000000 。
将其改为0x0即可使得虚拟地址与物理地址相等。