现在的位置: 首页 > 综合 > 正文

自己写操作系统–(12)

2019年05月27日 ⁄ 综合 ⁄ 共 9176字 ⁄ 字号 评论关闭

快找工作了,一直没更新,放假一周的时间抽了点工夫做了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的系统调用,较为简单:

[html] view
plain
copy

  1. static int  
  2. sys_env_set_pgfault_upcall(envid_t envid, void *func)  
  3. {  
  4.     // LAB 4: Your code here.  
  5.     struct Env* env;  
  6.         int ret=envid2env(envid,&env,1);  
  7.         if(ret<0)  
  8.             return ret;  
  9.     env->env_pgfault_upcall=func;  
  10.     cprintf("func :0x%x \r\n",func);  
  11.     return 0;  
  12.     //panic("sys_env_set_pgfault_upcall not implemented");  
  13. }  


Exercise 8 

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

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

[html] view
plain
copy

  1. void  
  2. page_fault_handler(struct Trapframe *tf)  
  3. {  
  4.     uint32_t fault_va;  
  5.     fault_va = rcr2();  
  6.     if((tf->tf_cs & 3)==0)  
  7.     {  
  8.         //内核态的错误依然没法处理  
  9.         print_trapframe(tf);  
  10.         panic("kernel mode page faults!!");  
  11.     }  
  12.     //判断用户是否给异常栈进行了映射  
  13.     user_mem_assert(curenv,(void*)(UXSTACKTOP-PGSIZE),PGSIZE,0);  
  14.     if(curenv->env_pgfault_upcall==NULL  )  
  15.     {  
  16.         //没有注册用户态的upcall函数  
  17.     cprintf("[%08x] user fault va %08x ip %08x\n",  
  18.         curenv->env_id, fault_va, tf->tf_eip);  
  19.     env_destroy(curenv);  
  20.     return ;  
  21.     }  
  22.     //构造数据结构,并复制,这个数据结构将传递给用户态的处理函数  
  23.     struct UTrapframe utf;  
  24.     memmove((void*)(&utf.utf_regs),(void*)(&(tf->tf_regs)),sizeof(tf->tf_regs));//复制寄存器  
  25.     (&utf)->utf_eflags=tf->tf_eflags;//复制flags  
  26.     (&utf)->utf_eip=tf->tf_eip;//复制eip  
  27.     (&utf)->utf_err=tf->tf_err;//复制err  
  28.     (&utf)->utf_esp=tf->tf_esp;//复制esp  
  29.     (&utf)->utf_fault_va=fault_va;  
  30.     int espaddr=0;  
  31.     if(tf->tf_esp>=UXSTACKTOP-PGSIZE && tf->tf_esp<=UXSTACKTOP-1)  
  32.     {  
  33.         //运行到这里说明是在用户态的异常处理函数里产生了异常  
  34.         struct Page* page=page_lookup(curenv->env_pgdir,(void*)(tf->tf_esp-4),0);  
  35.         if(page==NULL)  
  36.         {  
  37.             cprintf("non Page ...\r\n");  
  38.             page=page_alloc(ALLOC_ZERO);  
  39.         page_insert(curenv->env_pgdir,page,(void*)(tf->tf_esp-4),PTE_U|PTE_W);  
  40.         }  
  41.         memmove((void*)((tf->tf_esp)-4-sizeof(utf)),&utf,sizeof(utf));  
  42.         espaddr=tf->tf_esp-4-sizeof(utf);//新的栈顶  
  43.     }  
  44.     else  
  45.     {  
  46.     //将UTrapframe放到栈顶  
  47.     memmove((void*)(UXSTACKTOP-sizeof(utf)),&utf,sizeof(utf));  
  48.     espaddr=UXSTACKTOP-sizeof(utf);//改变栈指针,注意栈的生长是从高到底生长  
  49.     }  
  50.     struct Env *env=curenv;  
  51.     int calladdr=Paddr((int)env->env_pgfault_upcall);  
  52.     curenv->env_tf.tf_eip=(int)env->env_pgfault_upcall;//将eip设置为upcall  
  53.     curenv->env_tf.tf_esp=espaddr;//设置堆栈地址  
  54.     env_run(curenv);//返回用户态执行  
  55. }  


Exercise 9

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

  1. .text  
  2. .globl _pgfault_upcall  
  3. _pgfault_upcall:  
  4.     // Call the C page fault handler.  
  5.     pushl %esp          // function argument: pointer to UTF  
  6.     movl _pgfault_handler, %eax  
  7.     call *%eax  
  8.     addl $4, %esp           // pop function argument  
  9.       
  10.       
  11.    addl $8, %esp  
  12.    movl %esp,%eax  
  13.    addl $32,%esp  
  14.    popl %ebx  
  15.    addl $4,%esp  
  16.    popl %esp  
  17.    pushl %ebx  
  18.    movl %eax,%esp  
  19.    popal  
  20.    addl $4,%esp  
  21.    popf  
  22.    popl %esp  
  23.    subl $4,%esp  
  24.    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函数,较为简单

[html] view
plain
copy

  1. void  
  2. set_pgfault_handler(void (*handler)(struct UTrapframe *utf))  
  3. {  
  4.     int r;  
  5.     if (_pgfault_handler == 0) {  
  6.         //如果是第一次赋值,则要先非配异常栈,然后再设置upcall  
  7.         int envid=sys_getenvid();  
  8.         int r=sys_page_alloc(envid,(void*)UXSTACKTOP-PGSIZE,PTE_U|PTE_W|PTE_P);  
  9.         if(r<0)  
  10.         {  
  11.             panic("alloc uxstack fail");  
  12.         }  
  13.         sys_env_set_pgfault_upcall(envid, (void*) _pgfault_upcall);  
  14.   
  15.     }  
  16.   
  17.     // Save handler pointer for assembly to call.  
  18.     _pgfault_handler = handler;  
  19. }  


Exercise 11

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

[html] view
plain
copy

  1. static int  
  2. env_setup_vm(struct Env *e)  
  3. {  
  4.     int i;  
  5.     struct Page *p = NULL;  
  6.     cprintf("env_setup_vm\r\n");  
  7.     // Allocate a page for the page directory  
  8.     if (!(p = page_alloc(ALLOC_ZERO)))  
  9.         return -E_NO_MEM;  
  10.     e->env_pgdir=page2kva(p);  
  11.     for(i=PDX(UTOP);i<1024;i++)  
  12.     {  
  13.         if(kern_pgdir[i]!=0)  
  14.         {  
  15.   
  16.             struct Page* page=page_alloc(ALLOC_ZERO);  
  17.             e->env_pgdir[i]=(int)page2pa(page)|PTE_P|PTE_W|PTE_U;  
  18.             if(page==NULL)  
  19.             {  
  20.                 return -E_NO_MEM;  
  21.             }  
  22.             struct Page* kernpage=pa2page(PTE_ADDR(kern_pgdir[i]));  
  23.             memmove(page2kva(page),page2kva(kernpage),PGSIZE);  
  24.         }  
  25.   
  26.     }  
  27.     p->pp_ref++;  
  28.     page_insert(e->env_pgdir,p,(void*)UVPT,PTE_P|PTE_U);  
  29.     return 0;  
  30. }  


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

[html] view
plain
copy

  1. // implement fork from user space  
  2.   
  3. #include <inc/string.h>  
  4. #include <inc/lib.h>  
  5.   
  6. // PTE_COW marks copy-on-write page table entries.  
  7. // It is one of the bits explicitly allocated to user processes (PTE_AVAIL).  
  8. #define PTE_COW     0x800  
  9.   
  10. //  
  11. // Custom page fault handler - if faulting page is copy-on-write,  
  12. // map in our own private writable copy.  
  13. //  
  14. static void  
  15. pgfault(struct UTrapframe *utf)  
  16. {  
  17.     void *addr = (void *) utf->utf_fault_va;  
  18.     uint32_t err = utf->utf_err;  
  19.     int r;  
  20.     extern volatile pte_t vpt[];  
  21.     if((vpt[PDX(addr)] & (0 |PTE_W |PTE_COW))==0)  
  22.     {  
  23.         panic("PTE WRONG!!\r\n");  
  24.     }  
  25.   
  26.     int envid=sys_getenvid();  
  27.     int result=sys_page_alloc(envid,PFTEMP,PTE_U|PTE_W|PTE_P);  
  28.   
  29.     memmove(PFTEMP,ROUNDDOWN(addr,PGSIZE),PGSIZE);  
  30.     sys_page_map(envid,(void*) PFTEMP,envid,(void*)ROUNDDOWN(addr,PGSIZE), PTE_U|PTE_W|PTE_P);  
  31. }  
  32.   
  33. //  
  34. // Map our virtual page pn (address pn*PGSIZE) into the target envid  
  35. // at the same virtual address.  If the page is writable or copy-on-write,  
  36. // the new mapping must be created copy-on-write, and then our mapping must be  
  37. // marked copy-on-write as well.  (Exercise: Why do we need to mark ours  
  38. // copy-on-write again if it was already copy-on-write at the beginning of  
  39. // this function?)  
  40. //  
  41. // Returns: 0 on success, < 0 on error.  
  42. // It is also OK to panic on error.  
  43. //  
  44. static int  
  45. duppage(envid_t envid, unsigned pn)  
  46. {  
  47.     int r=sys_getenvid();  
  48.     int result=0;  
  49.     int perm=0;  
  50.     if(pn*PGSIZE==UXSTACKTOP-PGSIZE)  
  51.         return 0; //整个地址空间除异常栈之外全部进行重新映射  
  52.         perm = (perm |PTE_P| PTE_U|PTE_COW );  
  53.     result=sys_page_map(r, (void*)(pn*PGSIZE),envid, (void*)(pn*PGSIZE), perm);  
  54.     result=sys_page_map(r, (void*)(pn*PGSIZE),r, (void*)(pn*PGSIZE), perm);  
  55.     return 0;  
  56. }  
  57.   
  58. //  
  59. // User-level fork with copy-on-write.  
  60. // Set up our page fault handler appropriately.  
  61. // Create a child.  
  62. // Copy our address space and page fault handler setup to the child.  
  63. // Then mark the child as runnable and return.  
  64. //  
  65. // Returns: child's envid to the parent, 0 to the child, < 0 on error.  
  66. // It is also OK to panic on error.  
  67. //  
  68. // Hint:  
  69. //   Use vpd, vpt, and duppage.  
  70. //   Remember to fix "thisenv" in the child process.  
  71. //   Neither user exception stack should ever be marked copy-on-write,  
  72. //   so you must allocate a new page for the child's user exception stack.  
  73. //  
  74. envid_t  
  75. fork(void)  
  76. {  
  77.     // LAB 4: Your code here.  
  78.     //panic("fork not implemented");  
  79.     //cprintf("this is Fork!\r\n");  
  80.     set_pgfault_handler(pgfault);  
  81.     envid_t envid;  
  82.         uint8_t *addr;  
  83.         int r;  
  84.         extern unsigned char end[];  
  85.         envid = sys_exofork();  
  86.         if (envid < 0)  
  87.             panic("sys_exofork: %e", envid);  
  88.         if (envid == 0) {  
  89.             thisenv = &envs[ENVX(sys_getenvid())];  
  90.             return 0;  
  91.         }  
  92.     //  cprintf("user : create new env finish! %d\r\n",envid);  
  93.         sys_page_alloc(envid,(void*)UXSTACKTOP-PGSIZE,PTE_U|PTE_W|PTE_P);  
  94.         extern volatile pte_t vpt[];  
  95.         int i,j;  
  96.         for(i=0;i<=UTOP/PGSIZE-1;i++)  
  97.         {  
  98.             if((vpt[i/1024] &(0|PTE_P))!=0 )  
  99.             {  
  100.                     duppage(envid,i);  
  101.             }  
  102.         }  
  103.         if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)  
  104.             panic("sys_env_set_status: %e", r);  
  105.         cprintf("this is Fork finish!!\r\n");  
  106.         return envid;  
  107.   
  108. }  
  109.   
  110. // Challenge!  
  111. int  
  112. sfork(void)  
  113. {  
  114.     panic("sfork not implemented");  
  115.     return -E_INVAL;  
  116. }  

抱歉!评论已关闭.