填写已有实验
本实验依赖实验1和实验2.请把要做的实验1的代码填入本实验中代码有lab1、lab2的注释相应部分
发现缺失的是kdebug.c、trap.c、default_pmm.c、pmm.c
四个文件的相关代码,补全后进行下一练习
给未被映射的地址映射上物理页(需要编程)
完成do_pgfault(mm/vmm.c)
函数,给未被映射的地址映射上物理页。设置访问权限的时候需要参考页面所在VMA的权限,同时需要注意映射物理页时需要操作内存控制结构所制定的页表,而不是内核的页表。
在程序的执行过程中由于某种原因(页框不存在/写只读页等)而是CPU无法最终访问到相应的物理内存单元,即无法完成从虚拟地址到物理地址的映射是,CPU会产生一次页错误异常,从而需要进行相应的页错误异常服务例程。当相关处理完成后,将返回产生异常的指令处重新执行,使得软件正常运行。
当出现上面情况之一,就会产生页面page fault(#PF)
异常。产生异常的线性地址存储在
CR2中,并且将是page fault
的产生类型保存在 error code
中
因此此函数是完成页错误异常处理的主要函数,他根据从CPU的控制寄存器CR2中获取的页错误异常的虚拟地址,以及根据error code的错误类型来查找次虚拟地址是否在某个VMA的地址范围内,并且是否满足正确的读写权限,如果在此范围内并且权限也正确,就认为这是一次合法访问,但没有建立虚实对应关系,所以需要分配一个空闲的内存页,并修改页表完成虚地址到物理地址的映射,刷新TLB,然后调用iret中断,返回并重新执行。如果该虚地址不在某VMA范围内,这认为是一个非法访问
这里把vma_struct结构的变量简称为VMA变量。
struct vma_struct {
// the set of vma using the same PDT
struct mm_struct *vm_mm;
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma
uint32_t vm_flags; // flags of vma
//linear list link which sorted by start addr of vma
list_entry_t list_link;
};
vm_start
和vm_end
描述了一个连续地址的虚拟内存空间的起始位置和结束位置,描述的是一个合理的地址空间范围(即严格确保 vm_start < vm_end的关系);list_link
是一个双向链表,按照从小到大的顺序把一系列用vma_struct
表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct
应该是不相交的,即vma之间的地址空间无交集;vm_flags
表示了这个虚拟内存空间的属性,目前的属性包括#define VM_READ 0x00000001 //只读
#define VM_WRITE 0x00000002 //可读写
#define VM_EXEC 0x00000004 //可执行
vm_mm
是一个指针,指向一个比vma_struct
更高的抽象层次的数据结构mm_struct
这个数据结构表示了包含所有虚拟内存空间的共同属性。
struct mm_struct {
// linear list link which sorted by start addr of vma
list_entry_t mmap_list;
// current accessed vma, used for speed purpose
struct vma_struct *mmap_cache;
pde_t *pgdir; // the PDT of these vma
int map_count; // the count of these vma
void *sm_priv; // the private data for swap manager
};
mmap_list
是双向链表头,链接了所有属于同一页目录表的虚拟内存空间mmap_cache
是指向当前正在使用的虚拟内存空间pgdir
所指向的就是 mm_struct
数据结构所维护的页表map_count
记录mmap_list
里面链接的vma_struct
的个数sm_priv
指向用来链接记录页访问情况的链表头首先看下注释
/*LAB3 EXERCISE 1: YOUR CODE
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* get_pte : get an pte and return the kernel virtual address of this pte for la
* if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1')
* pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup
* an addr map pa<--->la with linear address la and the PDT pgdir
* DEFINES:
* VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable
* PTE_W 0x002 // page table/directory entry flags bit : Writeable
* PTE_U 0x004 // page table/directory entry flags bit : User can access
* VARIABLES:
* mm->pgdir : the PDT of these vma
*
*/
(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
根据注释修改代码
if (ptep == NULL)
{
cprintf("get_pte in do_pgfault failed\n");
goto failed;
}
// if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
//页表不存在
if (*ptep == 0)
{ //尝试分配一空闲页,匹配物理地址与逻辑地址,建立对应关系
if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {//失败内存不够退出
cprintf("pgdir_alloc_page in do_pgfault failed\n");
goto failed;
}
}
补充完成基于
FIFO
的页面替换算法(需要编程)
完成vmm.c
中的do_pgfault
函数,并且在实现FIFO
算法的swap_fifo.c
中完成map_swappable
和swap_out_victim
函数。通过对swap的测试
根据练习1,当页错误异常发生时,有可能是因为页面保存在swap区或者磁盘文件上造成的,所以我们需要利用页面替换算法解决这个问题。
页面替换主要分为两个方面,页面换出和页面换入。
- 页面换入主要在上述的do_pgfault()
函数实现;
- 页面换出主要在swap_out_vistim()
函数实现。
在换入时,需要先检查产生访问异常的地址是否属于某个VMA表示的合法虚拟地址,并且保存在硬盘的swap文件中(即对应的PTE的高24位不为0,而最低位为0),则是执行页换入的时机,将调用swap_in函数完成页面换入。
在换出时,采取的是消极的换出策略,是在调用alloc_pages函数获取空闲页时,此函数如果发现无法从物理内存页分配器(比如First Fit)获得空闲页,就会进一步调用swap_out函数 换出某页,实现一种消极的换出策略
查看注释
/*LAB3 EXERCISE 2: YOUR CODE
* Now we think this pte is a swap entry, we should load data from disk to a page with phy addr,
* and map the phy addr with logical addr, trigger swap manager to record the access situation of this page.
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr,
* find the addr of disk page, read the content of disk page into this memroy page
* page_insert : build the map of phy addr of an Page with the linear addr la
* swap_map_swappable : set the page swappable
*/
根据注释补充代码
else
{ // if this pte is a swap entry, then load data from disk to a page with phy addr
// and call page_insert to map the phy addr with logical addr
if(swap_init_ok)
{//页表项非空,根据mm结构和addr地址,尝试将硬盘中的内容换入page中
struct Page *page=NULL;
ret = swap_in(mm, addr, &page);
if (ret != 0)
{
cprintf("swap_in in do_pgfault failed\n");
goto failed;
}
//建立虚拟地址和物理地址之间的对应关系
page_insert(mm->pgdir, page, addr, perm);
//将此页面设置为可交换
swap_map_swappable(mm, addr, page, 1);
page->pra_vaddr = addr;
}
else
{
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}
首先是_fifo_map_swappable
,可用于建立页访问属性和关系,比如访问时间的先后顺序。
首先看下注释
//record the page access situlation
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1)link the most recent arrival page at the back of the pra_list_head qeueue.
根据注释写代码
/*
* (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue
*/
static int
_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in)
{
list_entry_t *head=(list_entry_t*) mm->sm_priv;
list_entry_t *entry=&(page->pra_page_link);
assert(entry != NULL && head != NULL);
//record the page access situlation
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1)link the most recent arrival page at the back of the pra_list_head qeueue.
//将最近用到的页面添加到次序的队尾
list_add(head, entry);
return 0;
}
然后是_fifo_swap_out_victim
,可用于实现挑选出要换出的页。
查看下注释
/* Select the victim */
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1) unlink the earliest arrival page in front of pra_list_head qeueue
//(2) set the addr of addr of this page to ptr_page
根据注释写代码
/*
* (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue,
* then set the addr of addr of this page to ptr_page.
*/
static int
_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick)
{
list_entry_t *head=(list_entry_t*) mm->sm_priv;
assert(head != NULL);
assert(in_tick==0);
/* Select the victim */
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1) unlink the earliest arrival page in front of pra_list_head qeueue
//(2) set the addr of addr of this page to ptr_page
/* Select the tail */
//指出需要被换出的页
list_entry_t *le = head->prev;
assert(head != le);
//le2page 宏可以根据链表元素,获得对应page的指针
struct Page *p = le2page(le, pra_page_link);
//将进来最早的页面从队列中删除
list_del(le);
assert(p != NULL);
//将这一页的地址存储在ptr_page中
*ptr_page = p;
return 0;
}
运行成功
通过本次实验,我了解物理内存管理中的连续空间分配算法的具体实现,熟悉掌握Page Fault异常处理和FIFO页替换算法的实现。对页面替换机制有了一个大题的了解,在试验中学习,收获较多。但还是对执行流程较为模糊,对页替换算法还是掌握不牢,需要进一步的巩固学习.