Some blocking issues in implementing user space fork in JOS

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
zhongshu_gu
Posts: 6
Joined: Tue Jan 22, 2008 9:47 pm
Location: Beijing, China

Some blocking issues in implementing user space fork in JOS

Post by zhongshu_gu »

In JOS, we fork at user space. If I am using dumbfork, which copy the entire address space to the child, it works well. But if I am using COW for the fork, there will be some error.
code like below

Code: Select all

// 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
extern void _pgfault_upcall(void);

//
// Custom page fault handler - if faulting page is copy-on-write,
// map in our own private writable copy.
//
static void
pgfault(struct UTrapframe *utf)
{
	cprintf("pgfault\n");
	void *addr = (void *) utf->utf_fault_va;
	uint32_t err = utf->utf_err;
	int r;
	uint32_t pn;
	envid_t envid = sys_getenvid();

	// Check that the faulting access was (1) a write, and (2) to a
	// copy-on-write page.  If not, panic.
	// Hint:
	//   Use the read-only page table mappings at vpt
	//   (see <inc/memlayout.h>).

	// LAB 4: Your code here.
	if(!(err & FEC_WR))
		panic("not a write operation");
	pn = VPN(addr);
	if(!(vpt[pn] & PTE_COW))
		panic("not copy-on-write page");

	// Allocate a new page, map it at a temporary location (PFTEMP),
	// copy the data from the old page to the new page, then move the new
	// page to the old page's address.
	// Hint:
	//   You should make three system calls.
	//   No need to explicitly delete the old page's mapping.
	
	// LAB 4: Your code here.
	if((r = sys_page_alloc(envid,PFTEMP,PTE_W|PTE_U|PTE_P)) < 0)
		panic("pgfault page alloc%e",r);
	memmove((void*)PFTEMP, (void*)ROUNDDOWN(addr,PGSIZE),PGSIZE);
	if((r = sys_page_map(envid,(void*)PFTEMP,envid,(void*)ROUNDDOWN(addr,PGSIZE),PTE_P|PTE_U|PTE_W)) < 0)
		panic("map failed:%e",r);
	if((r = sys_page_unmap(envid,(void*)PFTEMP)) < 0)
		panic("unmap failed:%e",r);	
	
	//panic("pgfault not implemented");
}

//
// 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 mark ours copy-on-write again
// if it was already copy-on-write?)
//
// 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;
	void *addr;
	pte_t pte;
	uint32_t perm = 0;
	addr = (void*)(pn*PGSIZE);
	struct Env *env;
	//envid2env(sys_getenvid(),&env,1);
	// LAB 4: Your code here.
	if((vpt[pn] & PTE_W) || (vpt[pn] & PTE_COW))
	{
		perm = PTE_U|PTE_P|PTE_COW;
		if((r = sys_page_map(0,addr,envid,addr,perm)) < 0)
			return r;
		if((r = sys_page_map(0,addr,0,addr,perm)) < 0)
			return r;
		//tlb_invalidate(env->env_pgdir,addr);
	}
	else
	{
		if((r = sys_page_map(sys_getenvid(),addr,envid,addr,PGOFF(vpt[pn]))) < 0)
			return r;
	}	
	//panic("duppage not implemented");
	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 "env" and the user exception stack 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.
	envid_t envid,curenvid;
	uint8_t * addr;
	uint32_t pdeno,pteno;
	int r;
	uint32_t pn = 0;
	extern unsigned char end[];	
	set_pgfault_handler(pgfault);
	envid = sys_exofork();
	//cprintf("envid:%08x,end:%08x,UTOP:%08x\n",envid,end,UTOP);
	if (envid < 0)
		panic("sys_exofork: %e", envid);
	if (envid == 0) {
		// We're the child.
		// The copied value of the global variable 'env'
		// is no longer valid (it refers to the parent!).
		// Fix it and return 0.
		cprintf("child\n");		
		env = &envs[ENVX(sys_getenvid())];
		return 0;
	}
	//cprintf("max pdeno:%08x,end :%08x\n",PDX(UTOP));
	for(pdeno = PDX(UTEXT);pdeno < PDX(UTOP);pdeno++)
	{
		if(!(vpd[pdeno] & (PTE_P)))
			continue;
		else
		{
			for(pteno = 0;pteno < NPTENTRIES;pteno++)
			{
				pn = (pdeno<<10) + pteno;
				//cprintf("pn:%08x\n",pn);				
				if(pn <= VPN(UXSTACKTOP - PGSIZE))
				{
					if(vpt[pn] & (PTE_P))
					{
						//cprintf("duppage,envid:%08x\n",envid);
						duppage(envid,pn);
					}
				}
				else
					break;
			}
		}
	}
    
	cprintf("map the page finished\n");
	
	if((r = sys_page_alloc(envid, (void*)(UXSTACKTOP - PGSIZE), PTE_U|PTE_P|PTE_W)) < 0)
			return r;
	cprintf("alloc the page for %08x uxstack finished\n",envid);

	
	//sys_env_set_pgfault_upcall(envid,_pgfault_upcall);
	//cprintf("set page fault upcall finished\n");
	if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
		panic("sys_env_set_status: %e", r);
	cprintf("%08x set status runnable finished\n",envid);
	return envid;

	//panic("fork not implemented");
}
I am not sure when will the fork return 0 to the child env. If it doesn't return to the child, the next env will be forked by the first env. And I think it is the root of this fault.
Whether the sys_env_set_status() and set the env to runnable will return it to the child? I have no idea. The debug information like below.

Code: Select all

Alloc page directory
Alloc pages array
Alloc envs array
trap_syscall: 306a006a
&trap_syscall: f0102c00
enabled interrupts: 1 2 4
             Setup timer interrupts via 8259A
enabled interrupts: 0 1 2 4
             unmasked timer interrupt
generate env_id:00001000
[00000000] new env 00001000
env create 00001000
generate env_id:00001001
[00000000] new env 00001001
env create 00001001
00000000 is runnable
00000001 is runnable
1001: I am ''
set page fault handler:00001001
page insert,env_id:00001001,env_pgdir:f1fb2000,page:f01cebbc,va:eebff000
generate env_id:00001002
[00001001] new env 00001002
map the page finished
00001002 set status runnable finished
generate env_id:00001003
[00001001] new env 00001003
pgfault
page insert,env_id:00001001,env_pgdir:f1fb2000,page:f01ceb80,va:007ff000
map the page finished
00001003 set status runnable finished
[00001001] exiting gracefully
[00001001] free env 00001001
00000000 is runnable
00000002 is runnable
00000003 is runnable
kernel panic at kern/trap.c:276: kernel page fault:eebffffc
And the dumbfork will works well like below

Code: Select all

envid_t
fork(void)
{
	// LAB 4: Your code here.
	envid_t envid;
	uint8_t *addr;
	int r;
	extern unsigned char end[];

	// Allocate a new child environment.
	// The kernel will initialize it with a copy of our register state,
	// so that the child will appear to have called sys_exofork() too -
	// except that in the child, this "fake" call to sys_exofork()
	// will return 0 instead of the envid of the child.
	envid = sys_exofork();
	cprintf("fork envid:%08x\n",envid);
	if (envid < 0)
		panic("sys_exofork: %e", envid);
	if (envid == 0) {
		// We're the child.
		// The copied value of the global variable 'env'
		// is no longer valid (it refers to the parent!).
		// Fix it and return 0.
		//cprintf("I am the child\n");
		env = &envs[ENVX(sys_getenvid())];
		return 0;
	}

	// We're the parent.
	// Eagerly copy our entire address space into the child.
	// This is NOT what you should do in your fork implementation.
	cprintf("end:%08x,UTEXT:%08x,UTOP:%08x\n",end,UTEXT,UTOP);
	for (addr = (uint8_t*) UTEXT; addr < end; addr += PGSIZE)
		duppage(envid, addr);

	// Also copy the stack we are currently running on.
	duppage(envid, ROUNDDOWN(&addr, PGSIZE));

	// Start the child environment running
	cprintf("set %08x runnable\n",envid);
	if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
		panic("sys_env_set_status: %e", r);

	return envid;

	//panic("fork not implemented");
}
Its duppage just memcpy the content of address space.
Please give me some suggestion.
Thanks
Post Reply