JOS學習筆記(十二)

快找工作了,一直沒更新,放假一週的時間抽了點工夫做了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 int
sys_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函數指針處執行?個人認爲,是因爲直接在內核態處理過於危險,用戶可以藉此注入高權限的惡意代碼。

void
page_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設置爲upcall
	curenv->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 UTF
	movl _pgfault_handler, %eax
	call *%eax
	addl $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函數,較爲簡單

void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
	int r;
	if (_pgfault_handler == 0) {
		//如果是第一次賦值,則要先非配異常棧,然後再設置upcall
		int 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 int
env_setup_vm(struct Env *e)
{
	int i;
	struct Page *p = NULL;
	cprintf("env_setup_vm\r\n");
	// Allocate a page for the page directory
	if (!(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_COW		0x800

//
// Custom page fault handler - if faulting page is copy-on-write,
// map in our own private writable copy.
//
static void
pgfault(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 int
duppage(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_t
fork(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!
int
sfork(void)
{
	panic("sfork not implemented");
	return -E_INVAL;
}






發佈了78 篇原創文章 · 獲贊 14 · 訪問量 48萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章