JOS学习笔记(十二)

来源:互联网 发布:anaconda ubuntu 16.04 编辑:程序博客网 时间:2024/05/22 14:48

快找工作了,一直没更新,放假一周的时间抽了点工夫做了LAB4的PART B,总体来说还是感觉比较难的,尤其是一段汇编代码和异常栈那乱七八糟的堆栈。


一、概述

本部分实验主要是实现一个copy on write的fork函数,第一步是实现一个用户态的page fault处理机制:首先用户态使用一个系统调用传递给内核态一个函数指针作为page fault的回调函数,接着当发生page fault时内核进行简单的判断将该函数需要的一个特殊数据结构压栈,再使用iret跳到用户态执行此回调函数,执行完之后接着继续执行原先的用户态函数。第二步是在此基础上实现一个copy on write的fork,首先复制父进程地址空间的映射(也就是页目录和页表),然后把对应的页表全部变成不可写,并在保留位加入特殊符号,因此当写操作时候会报page fault错,报错后转入用户态的,在用户态把出错的页面复制后进行重新映射,之后继续返回源程序执行。

看起来不复杂,实际调试起来非常繁琐,内核crash上百次后总算是调通了。


二、实验

Exercise 7

实现一个设置page_fault_upcall的系统调用,较为简单:

static intsys_env_set_pgfault_upcall(envid_t envid, void *func){// LAB 4: Your code here.struct Env* env;int ret=envid2env(envid,&env,1);if(ret<0)return ret;env->env_pgfault_upcall=func;cprintf("func :0x%x \r\n",func);return 0;//panic("sys_env_set_pgfault_upcall not implemented");}


Exercise 8 

实现内核态的page_fault处理函数,该函数负责跳转到用户态的upcall(也就是pfentry.S),并为用户态的page_fault_handler设置好参数。

为什么要在用户态进行处理,即使用env_run进而调用而不是直接跳转到upcall函数指针处执行?个人认为,是因为直接在内核态处理过于危险,用户可以借此注入高权限的恶意代码。

voidpage_fault_handler(struct Trapframe *tf){uint32_t fault_va;fault_va = rcr2();if((tf->tf_cs & 3)==0){//内核态的错误依然没法处理print_trapframe(tf);panic("kernel mode page faults!!");}//判断用户是否给异常栈进行了映射user_mem_assert(curenv,(void*)(UXSTACKTOP-PGSIZE),PGSIZE,0);if(curenv->env_pgfault_upcall==NULL  ){//没有注册用户态的upcall函数cprintf("[%08x] user fault va %08x ip %08x\n",curenv->env_id, fault_va, tf->tf_eip);env_destroy(curenv);return ;}//构造数据结构,并复制,这个数据结构将传递给用户态的处理函数struct UTrapframe utf;memmove((void*)(&utf.utf_regs),(void*)(&(tf->tf_regs)),sizeof(tf->tf_regs));//复制寄存器(&utf)->utf_eflags=tf->tf_eflags;//复制flags(&utf)->utf_eip=tf->tf_eip;//复制eip(&utf)->utf_err=tf->tf_err;//复制err(&utf)->utf_esp=tf->tf_esp;//复制esp(&utf)->utf_fault_va=fault_va;int espaddr=0;if(tf->tf_esp>=UXSTACKTOP-PGSIZE && tf->tf_esp<=UXSTACKTOP-1){//运行到这里说明是在用户态的异常处理函数里产生了异常struct Page* page=page_lookup(curenv->env_pgdir,(void*)(tf->tf_esp-4),0);if(page==NULL){cprintf("non Page ...\r\n");page=page_alloc(ALLOC_ZERO);page_insert(curenv->env_pgdir,page,(void*)(tf->tf_esp-4),PTE_U|PTE_W);}memmove((void*)((tf->tf_esp)-4-sizeof(utf)),&utf,sizeof(utf));espaddr=tf->tf_esp-4-sizeof(utf);//新的栈顶}else{//将UTrapframe放到栈顶memmove((void*)(UXSTACKTOP-sizeof(utf)),&utf,sizeof(utf));espaddr=UXSTACKTOP-sizeof(utf);//改变栈指针,注意栈的生长是从高到底生长}struct Env *env=curenv;int calladdr=Paddr((int)env->env_pgfault_upcall);curenv->env_tf.tf_eip=(int)env->env_pgfault_upcall;//将eip设置为upcallcurenv->env_tf.tf_esp=espaddr;//设置堆栈地址env_run(curenv);//返回用户态执行}

Exercise 9

完成pfentry.S,主要是在用户态的page_fault_handler结束后如何恢复现场并跳回原程序执行。

.text.globl _pgfault_upcall_pgfault_upcall:// Call the C page fault handler.pushl %esp// function argument: pointer to UTFmovl _pgfault_handler, %eaxcall *%eaxaddl $4, %esp// pop function argument   addl $8, %esp   movl %esp,%eax   addl $32,%esp   popl %ebx   addl $4,%esp   popl %esp   pushl %ebx   movl %eax,%esp   popal   addl $4,%esp   popf   popl %esp   subl $4,%esp   ret
这段代码较为难以阅读,首先给出_pgfault_handler结束后的堆栈:

// trap-time esp
// trap-time eflags
// trap-time eip
// utf_regs.reg_eax
// ...
// utf_regs.reg_esi
// utf_regs.reg_edi
// utf_err (error code)
// utf_fault_va            <-- %esp

然后按顺序汇编代码做了这么以下几件事:

首先esp+8,即跳过utf_fault_va和errcode,指向reg_edi。

然后把这个esp存放在eax中。

接着esp+32,即指向trap-time eip。

然后调用popl,此时eip存放在了ebx中,esp指向eflags

然后跳过eflags,指向trap-time esp

接着把这个esp出栈替代原先的esp。

把ebx里的内容,也就是trap-time eip压入新的堆栈里

将eax里的内容放入esp,此时esp又重新指向reg_edi

使用popal恢复所有寄存器

esp+4,跳过trap-time eip,然后popf恢复eflags。此时esp指向trap-time esp

接着此esp出栈并替换原esp。

然后esp-4,即指向我们之前压入的trap-time eip

调用ret,弹出指令后堆栈指向trap-time esp所指向的位置,程序能够正常执行。


Exercise 10

完成用户态的set_pgfault_handler函数,较为简单

voidset_pgfault_handler(void (*handler)(struct UTrapframe *utf)){int r;if (_pgfault_handler == 0) {//如果是第一次赋值,则要先非配异常栈,然后再设置upcallint envid=sys_getenvid();int r=sys_page_alloc(envid,(void*)UXSTACKTOP-PGSIZE,PTE_U|PTE_W|PTE_P);if(r<0){panic("alloc uxstack fail");}sys_env_set_pgfault_upcall(envid, (void*) _pgfault_upcall);}// Save handler pointer for assembly to call._pgfault_handler = handler;}

Exercise 11

首先我发现了一个我在env.c中env_setup_vm中的一个错误,我只复制了页目录,没有复制页表导致所有进程共享了一个页表,一个修改导致其余的也修改。下面是改正后的函数:

static intenv_setup_vm(struct Env *e){int i;struct Page *p = NULL;cprintf("env_setup_vm\r\n");// Allocate a page for the page directoryif (!(p = page_alloc(ALLOC_ZERO)))return -E_NO_MEM;    e->env_pgdir=page2kva(p);    for(i=PDX(UTOP);i<1024;i++)    {    if(kern_pgdir[i]!=0)    {    struct Page* page=page_alloc(ALLOC_ZERO);    e->env_pgdir[i]=(int)page2pa(page)|PTE_P|PTE_W|PTE_U;    if(page==NULL)    {    return -E_NO_MEM;    }    struct Page* kernpage=pa2page(PTE_ADDR(kern_pgdir[i]));    memmove(page2kva(page),page2kva(kernpage),PGSIZE);    }    }    p->pp_ref++;page_insert(e->env_pgdir,p,(void*)UVPT,PTE_P|PTE_U);return 0;}

给出fork.c整个文件,较为简单,即使出错也是因为一些粗心导致的错误。

// implement fork from user space#include <inc/string.h>#include <inc/lib.h>// PTE_COW marks copy-on-write page table entries.// It is one of the bits explicitly allocated to user processes (PTE_AVAIL).#define PTE_COW0x800//// Custom page fault handler - if faulting page is copy-on-write,// map in our own private writable copy.//static voidpgfault(struct UTrapframe *utf){void *addr = (void *) utf->utf_fault_va;uint32_t err = utf->utf_err;int r;extern volatile pte_t vpt[];if((vpt[PDX(addr)] & (0 |PTE_W |PTE_COW))==0){panic("PTE WRONG!!\r\n");}int envid=sys_getenvid();int result=sys_page_alloc(envid,PFTEMP,PTE_U|PTE_W|PTE_P);memmove(PFTEMP,ROUNDDOWN(addr,PGSIZE),PGSIZE);sys_page_map(envid,(void*) PFTEMP,envid,(void*)ROUNDDOWN(addr,PGSIZE), PTE_U|PTE_W|PTE_P);}//// Map our virtual page pn (address pn*PGSIZE) into the target envid// at the same virtual address.  If the page is writable or copy-on-write,// the new mapping must be created copy-on-write, and then our mapping must be// marked copy-on-write as well.  (Exercise: Why do we need to mark ours// copy-on-write again if it was already copy-on-write at the beginning of// this function?)//// Returns: 0 on success, < 0 on error.// It is also OK to panic on error.//static intduppage(envid_t envid, unsigned pn){int r=sys_getenvid();    int result=0;int perm=0;if(pn*PGSIZE==UXSTACKTOP-PGSIZE)return 0; //整个地址空间除异常栈之外全部进行重新映射perm = (perm |PTE_P| PTE_U|PTE_COW );result=sys_page_map(r, (void*)(pn*PGSIZE),envid, (void*)(pn*PGSIZE), perm);result=sys_page_map(r, (void*)(pn*PGSIZE),r, (void*)(pn*PGSIZE), perm);return 0;}//// User-level fork with copy-on-write.// Set up our page fault handler appropriately.// Create a child.// Copy our address space and page fault handler setup to the child.// Then mark the child as runnable and return.//// Returns: child's envid to the parent, 0 to the child, < 0 on error.// It is also OK to panic on error.//// Hint://   Use vpd, vpt, and duppage.//   Remember to fix "thisenv" in the child process.//   Neither user exception stack should ever be marked copy-on-write,//   so you must allocate a new page for the child's user exception stack.//envid_tfork(void){// LAB 4: Your code here.//panic("fork not implemented");//cprintf("this is Fork!\r\n");set_pgfault_handler(pgfault);envid_t envid;uint8_t *addr;int r;extern unsigned char end[];envid = sys_exofork();if (envid < 0)panic("sys_exofork: %e", envid);if (envid == 0) {thisenv = &envs[ENVX(sys_getenvid())];return 0;}//cprintf("user : create new env finish! %d\r\n",envid);sys_page_alloc(envid,(void*)UXSTACKTOP-PGSIZE,PTE_U|PTE_W|PTE_P);extern volatile pte_t vpt[];int i,j;        for(i=0;i<=UTOP/PGSIZE-1;i++)        {        if((vpt[i/1024] &(0|PTE_P))!=0 )        {        duppage(envid,i);        }        }if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)panic("sys_env_set_status: %e", r);cprintf("this is Fork finish!!\r\n");return envid;}// Challenge!intsfork(void){panic("sfork not implemented");return -E_INVAL;}






原创粉丝点击