OS透過Page Tables的機制來控制memory,而xv6主要就是利用page tables來區分address space以及保護memory,同時亦使用一些page table的簡單技巧:
x86 page table的組成:
Page table被儲存在physical memory內,有著類似two-level tree的結構:

address translation包含兩個步驟:
如果page directory entry或者PTE是不存在的,就會造成page hardware錯誤。每一個PTE包含一些flag bits用來說明virtual addess的使用權限,flag bits包含:

Entry所建立的page tale是足夠來讓kernel的C運行的,然而main仍然會呼叫kvmalloc來轉換到新的page table,因為kernel更精巧的描述process address space。
void kvmalloc(void)
{
kpgdir = setupkvm();
switchkvm();
}
一個process的user memory從virtual address的0開始,最多能增長到KERNBASE,這讓process最多只能使用2GB的memory。當一個process向xv6要求更多的mamory的時候:

main會呼叫kvmalloc來建立以及轉換到一個能夠mapping到KERNBASE之上的page table來供kernel運行,而這裡多數的工作都是由setupkvm所完成的。
pde_t* setupkvm(void)
{
pde_t *pgdir;
struct kmap *k;
if((pgdir=(pde_t*)kalloc())==0)
return 0;
memset(pgdir,0,PGSIZE);
if(P2V(PHYSTOP)>(void*)DEVSPACE)
panic("PHYSTOP too high");
for(k=kmap;k<&kmap[NELEM(kmap)];k++)
if(mappages(pgdir,k->virt,k->phys_end - k->phy_s_start(uint)k->phy_s_start,k->perm)<0){
freevm(pgdir);
return 0;
}
}
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
char *a, *last;
pte_t *pte;
a = (char*)PGROUNDDOWN((uint)va);
last = (char*)PGROUNDDOWN(((uint)va) + size − 1);
for(;;)
{
if((pte = walkpgdir(pgdir, a, 1)) == 0)
return −1;
if(*pte & PTE_P)
panic("remap");
*pte = pa | perm | PTE_P;//紀錄PTE是否為有效
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
static pte_t *walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
pde_t *pde;
pte_t *pgtab;
pde = &pgdir[PDX(va)];//找尋page directory entry
if(*pde & PTE_P)
{
pgtab = (pte_t*)P2V(PTE_ADDR(*pde));
}
else
{
if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
return 0;
memset(pgtab, 0, PGSIZE);
*pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U;
}
return &pgtab[PTX(va)];//找到page table pgae內的PTE
}
Allocator的資料結構是一個physical memory pages的free list
struct run
{
struct run *next;
};//free page's list的element
//用來保護free list的spin lock
struct
{
struct spinlock lock;
int use_lock;
struct run *freelist;
} kmem;
void kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
freerange(vstart, vend);
}
void kinit2(void *vstart, void *vend)
{
freerange(vstart, vend);
kmem.use_lock = 1;
}
void kfree(char *v)
{
struct run *r;
if((uint)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
panic("kfree");
memset(v, 1, PGSIZE);
if(kmem.use_lock)
acquire(&kmem.lock);
r = (struct run*)v;
r−>next = kmem.freelist;
kmem.freelist = r;
if(kmem.use_lock)
release(&kmem.lock);
}
每一個user process從adderss 0開始,最底層包含了user program、data以及stack;而heap在stack之上如此一來當process呼叫sbrk才能擴大。

Sbrk是一個system call來讓process增減其memory,這個system call是由growproc這個function來執行。
int growproc(int n)
{
uint sz;
struct proc *curproc = myproc();
sz = curproc−>sz;
if(n > 0)
{
if((sz = allocuvm(curproc−>pgdir, sz, sz + n)) == 0)
return −1;
}
else if(n < 0)
{
if((sz = deallocuvm(curproc−>pgdir, sz, sz + n)) == 0)
return −1;
}
curproc−>sz = sz;
switchuvm(curproc);
return 0;
}
Exec是system call,建立使用者部分的address space,並且使用文件系統的文件來初始化部分只用者的address space。
int exec(char *path, char **argv)
{
char *s, *last;
int i, off;
uint argc, sz, sp, ustack[3+MAXARG+1];
struct elfhdr elf;
struct inode *ip;
struct proghdr ph;
pde_t *pgdir, *oldpgdir;
struct proc *curproc = myproc();
begin_op();
if((ip = namei(path)) == 0)
{
end_op();
cprintf("exec: fail\n");
return −1;
}
ilock(ip);
pgdir = 0;
if(readi(ip, (char*)&elf, 0, sizeof(elf)) != sizeof(elf))
goto bad;
if(elf.magic != ELF_MAGIC)
goto bad;
if((pgdir = setupkvm()) == 0)
goto bad;
//把program讀進memory
sz = 0;
for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph))
{
if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
goto bad;
if(ph.type != ELF_PROG_LOAD)
continue;
if(ph.memsz < ph.filesz)
goto bad;
if(ph.vaddr + ph.memsz < ph.vaddr)
goto bad;
if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
goto bad;
if(ph.vaddr % PGSIZE != 0)
goto bad;
if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
goto bad;
}
iunlockput(ip);
end_op();
ip = 0;
//在下一個page boundary allocate兩個pages
sz = PGROUNDUP(sz);
if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)
goto bad;
clearpteu(pgdir, (char*)(sz − 2*PGSIZE));
sp = sz;
for(argc = 0; argv[argc]; argc++)
{
if(argc >= MAXARG)
goto bad;
sp = (sp − (strlen(argv[argc]) + 1)) & ~3;
if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
goto bad;
ustack[3+argc] = sp;
}
ustack[3+argc] = 0;
ustack[0] = 0xffffffff; // fake return PC
ustack[1] = argc;
ustack[2] = sp − (argc+1)*4; // argv pointer
sp −= (3+argc+1) * 4;
if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0)
goto bad;
for(last=s=path; *s; s++)
if(*s == ’/’)
last = s+1;
safestrcpy(curproc−>name, last, sizeof(curproc−>name));
oldpgdir = curproc−>pgdir;
curproc−>pgdir = pgdir;
curproc−>sz = sz;
curproc−>tf−>eip = elf.entry; // main
6700 curproc−>tf−>esp = sp;
switchuvm(curproc);//建置新的image
freevm(oldpgdir);//釋放舊的image
return 0;
bad:
if(pgdir)
freevm(pgdir);//釋放新的image
if(ip)
{
iunlockput(ip);
end_op();
}
return −1;
}