借助于页表机制和实验一涉及的中断异常处理机制,完成PageFault异常处理和FIFO算法的实现,结合磁盘提供的缓存空间,从而能够支持虚存管理。
设置访问权限的时候需要参考页面所在的VMA的权限,映射物理页时需要操作内存控制结构所指定的页表,不是内核的页表。
启动分页机制后,如果一条指令或数据的虚拟地址所对应的物理页不在内存中,或者访问权限不够,就会产生页错误异常。
1.页表全为0–虚拟地址与物理地址未建立映射关系或者关系撤销
2.物理页不在内存中,需要进行换页机制
3.访问权限不够,报错
//try to find a vma which include addr
// mm->mmap_cache = NULL;
// mm->pgdir = NULL;
// mm->map_count = 0;
// if (swap_init_ok) swap_init_mm(mm);
// else mm->sm_priv = NULL;
struct vma_struct *vma = find_vma(mm, addr);
//pagefault_num的初始值为0
pgfault_num++;
/*
the virtual continuous memory area(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
uint32_t vm_flags; // flags of vma
list_entry_t list_link; // linear list link which sorted by start addr of vma
};
*/
//尝试获得页表入口
if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) {
cprintf("get_pte in do_pgfault failed\n");
goto failed;
}
//页表不存在,页表项全为0
if (*ptep == 0) { // if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
//perm设置权限
//尝试申请一个页,如果申请失败,就是内存不足了,退出
if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
cprintf("pgdir_alloc_page in do_pgfault failed\n");
goto failed;
}
}
//页表项非空,尝试换入页面
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) {
struct Page *page=NULL;
//根据mm结构和addr地址,尝试将硬盘中的内容换入page中,此时page还没有加入到队列中
if ((ret = swap_in(mm, addr, &page)) != 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);
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}
ret = 0;
failed:
return ret;
述页目录项(Pag Director Entry)和页表(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。
[提示]页访问异常错误码有32位。位0为1表示对应物理页不存在;位1为1表示写异常(比如写了只读页;位2为1表示访问权限异常(比如用户态程序访问内核空间的数据)
[提示] CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中对应的中断服务例程可以检查CR2的内容,从而查出线性地址空间中的哪个页引起本次异常。
产生页访问异常后,CPU硬件和软件都会做一些事情来应对此事。首先页访问异常也是一种异常,所以针对一般异常的硬件处理操作是必须要做的,即CPU在当前内核栈保存当前被打断的程序现场,即依次压入当前被打断程序使用的EFLAGS,CS,EIP,errorCode;由于页访问异常的中断号是0xE,CPU把异常中断号0xE对应的中断服务例程的地址(vectors.S中的标号vector14处)加载到CS和EIP寄存器中,开始执行中断服务例程。这时ucore开始处理异常中断,首先需要保存硬件没有保存的寄存器。在vectors.S中的标号vector14处先把中断号压入内核栈,然后再在trapentry.S中的标号__alltraps处把DS、ES和其他通用寄存器都压栈。自此,被打断的程序执行现场(context)被保存在内核栈中。接下来,在trap.c的trap函数开始了中断服务例程的处理流程,大致调用关系为:
trap–> trap_dispatch–>pgfault_handler–>do_pgfault
产生页访问异常后,CPU把引起页访问异常的线性地址装到寄存器CR2中,并给出了出错码errorCode,说明了页访问异常的类型。ucore OS会把这个值保存在struct trapframe 中tf_err成员变量中。而中断服务例程会调用页访问异常处理函数do_pgfault进行具体处理。这里的页访问异常处理是实现按需分页、页换入换出机制的关键之处。
ucore中do_pgfault函数是完成页访问异常处理的主要函数,它根据从CPU的控制寄存器CR2中获取的页访问异常的物理地址以及根据errorCode的错误类型来查找此地址是否在某个VMA的地址范围内以及是否满足正确的读写权限,如果在此范围内并且权限也正确,这认为这是一次合法访问,但没有建立虚实对应关系。所以需要分配一个空闲的内存页,并修改页表完成虚地址到物理地址的映射,刷新TLB,然后调用iret中断,返回到产生页访问异常的指令处重新执行此指令。如果该虚地址不在某VMA范围内,则认为是一次非法访问。
vmm.c的do_pgfault
if(swap_init_ok) {
struct Page *page=NULL;
//根据mm结构和addr地址,尝试将硬盘中的内容换入page中,此时page还没有加入到队列中
if ((ret = swap_in(mm, addr, &page)) != 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);
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
swap _fifo.c
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: 2013011424*/
//(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
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: 2013011424*/
//(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 */
//用le指示需要被换出的页
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;
}
操作系统淘汰页面时,对当前指针指向的页所对应的页表项进行查询,如果dirty bit为0,把页换到硬盘上,如果dirty bit 为1,则将dirty bit改为0,继续访问下一页
编写_extend_clock()然后更改默认替换算法.
mm/swap_fifo.c
struct swap_manager swap_manager_fifo =
{
.name = "fifo swap manager",
.init = &_fifo_init,
.init_mm = &_fifo_init_mm,
.tick_event = &_fifo_tick_event,
.map_swappable = &_fifo_map_swappable,
.set_unswappable = &_fifo_set_unswappable,
// .swap_out_victim = &_fifo_swap_out_victim,
//更改默认的算法
.swap_out_victim = &_extended_clock_swap_out_victim,
.check_swap = &_fifo_check_swap,
};
在mm/swap_fifo.c添加函数
static int _extended_clock_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);
list_entry_t*le=head->prev;
assert(head!=le);
while(le!=head)
{
struct Page*p=le2page(le,pra_page_link);
//获取页表项
pte_t*ptep=get_pte(mm->pgdir,p->pra_vaddr,0);
//判断获得的页表项是否正确
if(*ptep<0x1000)
{
break;
}
if(!(*ptep&PTE_D))
{
//如果dirty bit 为0,换出页面,将页面从队列中删除
list_del(le);
assert(p!=NULL);
//将这一个页放在ptr_page中
*ptr_page=p;
return 0;
}else{
//如果为1 ,赋值为0跳过
*ptep&=0xffffffbf;
}
le=le->prev;
}
le=head->prev;
assert(head!=le);
struct Page*p=le2page(le,pra_page_link);
list_del(le);
assert(p!=NULL);
*ptr_page=p;
return 0;
}