练习1:给未被映射的地址映射上物理页
page_fault函数不知道哪些是“合法”的虚拟页,原因是ucore还缺少一定的数据结构来描述这种不在物理内存中的“合法”虚拟页。为此ucore通过建立mm_struct和vma_struct数据结构,描述了ucore模拟应用程序运行所需的合法内存空间。
当访问内存产生page fault异常时,可获得访问的内存的方式(读或写)以及具体的虚拟内存地址,这样ucore就可以查询此地址,看是否属于vma_struct数据结构中描述的合法地址范围中,如果在,则可根据具体情况进行请求调页/页换入换出处理;如果不在,则报错。
虚拟地址空间和物理地址空间的示意图:
注释中介绍了mm_struct(mm)
和vma_struct(vma)
/*
vmm design include two parts: mm_struct (mm) & vma_struct (vma)
mm is the memory manager for the set of continuous virtual memory
area which have the same PDT. vma is a continuous virtual memory area.
There a linear link list for vma & a redblack link list for vma in mm.
---------------
mm related functions:
golbal functions
struct mm_struct * mm_create(void)
void mm_destroy(struct mm_struct *mm)
int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)
--------------
vma related functions:
global functions
struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...)
void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma)
struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr)
local functions
inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next)
---------------
check correctness functions
void check_vmm(void);
void check_vma_struct(void);
void check_pgfault(void);
*/
vma_struct
:用于管理应用程序的虚拟内存,是描述应用程序对虚拟内存“需求”的结构
// the virtual continuous memory area(vma), [vm_start, vm_end),
// addr belong to a vma means vma.vm_start<= addr <vma.vm_end
struct vma_struct {
struct mm_struct *vm_mm; // the set of vma using the same PDT
uintptr_t vm_start; // vma的开始地址
uintptr_t vm_end; // vma的结束地址,不包括vm_end本身
uint32_t vm_flags; // flags of vma(包括可读、可写、可执行)
list_entry_t list_link;
// linear list link which sorted by start addr of vma
};
//vm_flags
#define VM_READ 0x00000001
#define VM_WRITE 0x00000002
#define VM_EXEC 0x00000004
mm_struct
:
// the control struct for a set of vma using the same PDT
struct mm_struct {
list_entry_t mmap_list;
// linear list link which sorted by start addr of vma
struct vma_struct *mmap_cache;
// current accessed vma, used for speed purpose
//指向当前正在使用的虚拟内存空间
pde_t *pgdir; // 指向的mm_struct数据结构所维护的一级页表
int map_count; // 记录mmap_list里面链接的vma_struct的个数
void *sm_priv;
// 指向用来链接记录页访问情况的链表头。(用于FIFO替换策略的访问。)
};
当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等),就会发生页访问异常。产生页访问异常的原因主要有:
- 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
- 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上),这在本次实验中会出现,我们将在下面介绍换页机制实现时进一步讲解如何处理;
- 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面).
当出现上面情况之一,那么就会产生页面page fault(#PF)异常。
CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。
do_pgfault()
的调用关系图:
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()
。这和调用关系图完全吻合。
完成do_pgfault()
函数:
int
do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) {
int ret = -E_INVAL;
//try to find a vma which include addr
//尝试寻找包括addr的vma
struct vma_struct *vma = find_vma(mm, addr);
pgfault_num++;
//If the addr is in the range of a mm's vma?
if (vma == NULL || vma->vm_start > addr) {
cprintf("not valid addr %x, and can not find it in vma\n", addr);
goto failed;
}
//check the error_code
switch (error_code & 3) {
//错误处理
default:
/* error code flag : default is 3 ( W/R=1, P=1): write, present */
case 2: /* error code flag : (W/R=1, P=0): write, not present */
if (!(vma->vm_flags & VM_WRITE)) {
cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n");
goto failed;
}
break;
case 1: /* error code flag : (W/R=0, P=1): read, present */
cprintf("do_pgfault failed: error code flag = read AND present\n");
goto failed;
//如果无法读取直接报错
case 0: /* error code flag : (W/R=0, P=0): read, not present */
if (!(vma->vm_flags