Some blocking issues in implementing user space fork in JOS
Posted: Sat Mar 01, 2008 12:55 am
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
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.
And the dumbfork will works well like below
Its duppage just memcpy the content of address space.
Please give me some suggestion.
Thanks
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");
}
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
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");
}
Please give me some suggestion.
Thanks