vma: 描述了一块连续的虚拟内存空间,保证start<=end,list_link
是一个双向链表,按照从小到大的顺序把一系列用vma_struct
表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct
应该是不相交的,即vma之间的地址空间无交集。
struct vma_struct {
struct mm_struct *vm_mm; // the set of vma using the same PDT
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma, not include the vm_end itself
uint32_t vm_flags; // flags of vma
list_entry_t list_link; // linear list link which sorted by start addr of vma
};
mm_struct:管理使用同一PDT的vma集合的结构体
struct mm_struct {
list_entry_t mmap_list; //双向链表头,链接了所有属于同一页目录表的虚拟内存空间
struct vma_struct *mmap_cache; //指向当前正在使用的虚拟内存空间
pde_t *pgdir; //指向的就是 mm_struct数据结构所维护的页表
int map_count; //记录mmap_list里面链接的vma_struct的个数
void *sm_priv; //指向用来链接记录页访问情况的链表头
};
mmap_list是双向链表头,链接了所有属于同一页目录表的虚拟内存空间,mmap_cache是指向当前正在使用的虚拟内存空间,由于操作系统执行的“局部性”原理,当前正在用到的虚拟内存空间在接下来的操作中可能还会用到,这时就不需要查链表,而是直接使用此指针就可找到下一次要用到的虚拟内存空间。pgdir 所指向的就是 mm_struct数据结构所维护的页表。通过访问pgdir可以查找某虚拟地址对应的页表项是否存在以及页表项的属性等。map_count记录mmap_list 里面链接的 vma_struct的个数。sm_priv指向用来链接记录页访问情况的链表头,这建立了mm_struct和后续要讲到的swap_manager之间的联系。
当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等),就会发生页访问异常。产生页访问异常的原因主要有:
目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上),这在本次实验中会出现,我们将在下面介绍换页机制实现时进一步讲解如何处理;
不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面).
当出现上面情况之一,那么就会产生页面page fault(#PF)异常。
CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。
do_pgfault()函数从CR2寄存器中获取页错误异常的虚拟地址,根据error code来查找这个虚拟地址是否在某一个VMA的地址范围内,并且具有正确的权限。如果满足上述两个要求,则需要为分配一个物理页。
所以出现page fault后,会有一个中断状态指针tf,传到trap()中处理:
void
trap(struct trapframe *tf) {
// dispatch based on what type of trap occurred
trap_dispatch(tf);
}
调用trap_dispatch():
static void
trap_dispatch(struct trapframe *tf) {
char c;
int ret;
switch (tf->tf_trapno) {
case T_PGFLT: //page fault页访问错误
if ((ret = pgfault_handler(tf)) != 0) {
print_trapframe(tf);
panic("handle pgfault failed. %e\n", ret);
}
break;
因为此时应该是page fault,所以调用pgfault_handler():
static int
pgfault_handler(struct trapframe *tf) {
extern struct mm_struct *check_mm_struct;
print_pgfault(tf);
if (check_mm_struct != NULL) {
return do_pgfault(check_mm_struct, tf->tf_err, rcr2());
}
//CR2存储了产生异常的线性地址
panic("unhandled page fault.\n");
}
然后调用了do_pgfault()
给未被映射的地址映射上物理页。设置访问权限 的时候需要参考页面所在 VMA 的权限,同时需要注意映射物理页时需要操作内存控制 结构所指定的页表,而不是内核的页表。
ptep = get_pte(mm->pgdir , addr , 1); //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
if(ptep == NULL){
cprintf("get_pte in do_pgfault failed\n");
goto failed;
}
if (*ptep == 0) { //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
if(pgdir_alloc_page(mm->pgdir , addr , perm) == NULL){
cprintf("pgdir_alloc_page in do_pgfault failed\n");
goto failed;
}
}
请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
页替换算法接口
struct swap_manager
{
const char *name;
/* Global initialization for the swap manager */
int (*init) (void);
/* Initialize the priv data inside mm_struct */
int (*init_mm) (struct mm_struct *mm);
/* Called when tick interrupt occured */
int (*tick_event) (struct mm_struct *mm);
/* Called when map a swappable page into the mm_struct */
int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in);
/* When a page is marked as shared, this routine is called to
* delete the addr entry from the swap manager */
int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr);
/* Try to swap out a page, return then victim */
int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick);
/* check the page relpacement algorithm */
int (*check_swap)(void);
};
do_pgfault()函数中的部分代码:用于执行页面从磁盘获取并设置页面可替换
if(swap_init_ok) {
struct Page *page=NULL;
if(swap_in(mm , addr , &page) != 0 ){ //根据mm和addr尝试在磁盘中加载对应的页
cprintf("swap_in in do_pgfault failed\n");
goto failed;
}
page_insert(mm->pgdir , page ,addr , perm); //(2) 插入页并对物理地址和虚拟地址映射
swap_map_swappable(mm, addr , page , 1); //(3) 设置为可被切换
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}
_fifo_swap_out_vistim
函数主要实现页面的换出,_fifo_map_swappable
用于添加页面并记录内容。
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;
}
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
list_entry_t *ulink_pg = head->prev;
assert(head != ulink_pg);
list_del(ulink_pg);
struct Page *page = le2page(ulink_pg , pra_page_link);
assert(page != NULL);
*ptr_page = page;
return 0;
}