環(huán)境
ubuntu 18.04 64位系統(tǒng)
HW地址:HW2 lazy page alloction
雖然官網(wǎng)沒有要求去閱讀trap這一章兔簇,但是我覺得還是讀一下對(duì)于本次實(shí)現(xiàn)的代碼有些幫助硬耍,而且還能基本清楚xv6是如何實(shí)現(xiàn)一個(gè)中斷和trap的。上一次的system call雖然我們實(shí)現(xiàn)了一個(gè)新的system call经柴,但是對(duì)于一個(gè)system call以及中斷這些調(diào)用的過程還沒有完全理解,閱讀一下這一章應(yīng)該可以理解個(gè)大概坯认。
正文
操作系統(tǒng)使用頁表的硬件來完成一些很巧妙的技巧之一就是對(duì)堆內(nèi)存來懶分配(lazy applocation)。Xv6程序使用系統(tǒng)調(diào)用sbrk()來申請(qǐng)堆內(nèi)存陋气。在現(xiàn)有的xv6源碼當(dāng)中,sbrk()分配了物理內(nèi)存并且將新分配的物理內(nèi)存同進(jìn)程的虛擬內(nèi)存映射起來。有一些程序申請(qǐng)了內(nèi)存巩趁,但是并沒有使用這些內(nèi)存,比如說一個(gè)很大的稀疏矩陣晶渠。復(fù)雜的內(nèi)核會(huì)延遲每一個(gè)內(nèi)存頁的分配知道程序真正需要這個(gè)頁--會(huì)觸發(fā)page fault(因?yàn)闆]有分配頁,所以會(huì)觸發(fā)page fault)褒脯。本次實(shí)驗(yàn)的目的就是在xv6中實(shí)現(xiàn)lazy page allocation。
第一部分:
第一部分要做的就是在sbrk()移除page allocation的代碼番川。sbrk()會(huì)掉用sys_sbrk()來實(shí)現(xiàn)內(nèi)存的分配,所以我們要去修改sys_sbrk()的代碼(sys_sbrk在sysproc.c中)颁督。sbrk(n)這個(gè)系統(tǒng)調(diào)用將進(jìn)程的內(nèi)存范圍增長(zhǎng)n字節(jié),然后返回新分配的內(nèi)存塊的地址(也就是原來內(nèi)存的大谐劣)。新的sbrk(n)應(yīng)該只增長(zhǎng)進(jìn)程的內(nèi)存大小(myproc()->sz)然后返回old size伐谈。它應(yīng)該只增加進(jìn)程內(nèi)存大小,但是不分配內(nèi)存诵棵。
實(shí)現(xiàn)代碼后祝旷,編譯,然后在shell 里面輸入echo hi 怀跛,你應(yīng)該會(huì)看到下面的結(jié)果:
init: starting sh
$ echo hi
pid 3 sh: trap 14 err 6 on cpu 0 eip 0x12f1 addr 0x4004--kill proc
$
錯(cuò)誤信息pid 3 sh:trap...來自trap.c,因?yàn)閠rap.c不知道如何去處理page fault(中斷號(hào)14,也就是T_PGFLT)吻谋。0x4004表示導(dǎo)致發(fā)生錯(cuò)誤的虛擬地址。
代碼實(shí)現(xiàn)
因?yàn)榍懊嬲f了sbrk()會(huì)掉用sys_sbrk()來實(shí)現(xiàn)內(nèi)存的申請(qǐng)什湘,所以我們只需要去修改sys_sbrk()的代碼就行了长赞。原來的代碼就不貼了。
int
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0) //從棧當(dāng)中獲得參數(shù)得哆,并且放到n當(dāng)中,看trap那一節(jié)可以理解的
return -1;
addr = myproc()->sz;
myproc()->sz += n; //只增加內(nèi)次大小栋操,但是沒有分配實(shí)際的內(nèi)存
return addr; //返回原來的size
}
下面是我的實(shí)驗(yàn)結(jié)果:
可以看到和上面題目說的輸出結(jié)果是一樣的,錯(cuò)誤的原因是在trap.c里面沒有代碼來處理page fault(中斷號(hào)14)矾芙。下面的 代碼就在trap.c當(dāng)中舍沙,上面的輸出結(jié)果就這里來的拂铡。
default:
if (myproc() == 0 || (tf->cs & 3) == 0)
{
// In kernel, it must be our mistake.
cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
tf->trapno, cpuid(), tf->eip, rcr2());
panic("trap");
}
// In user space, assume process misbehaved.
cprintf("pid %d %s: trap %d err %d on cpu %d "
"eip 0x%x addr 0x%x--kill proc\n",
myproc()->pid, myproc()->name, tf->trapno,
tf->err, cpuid(), tf->eip, rcr2());
myproc()->killed = 1;
}
第二部分: lazy page allocation
修改trap.c中的代碼葱绒,通過新申請(qǐng)一個(gè)物理內(nèi)存頁并且將這個(gè)頁映射到錯(cuò)誤的地址,然后返回到用戶程序繼續(xù)執(zhí)行接下來的代碼地淀。你應(yīng)該在cprintf輸出錯(cuò)誤信息之前加上你的代碼。你不需要覆蓋所有的邊界條件帮毁,只需要能夠運(yùn)行一些簡(jiǎn)單的命令就行了,比如說echo,ls等等
第二部分的關(guān)鍵是下面幾個(gè)提示:
- 提示: 看看cprintf的參數(shù)作箍,如何得到引發(fā)page fault的地址
- 從allocuvm()中偷學(xué)一點(diǎn)代碼,sbrk()通過調(diào)用growporc(),然后gorwproc()調(diào)用allocuvm()來實(shí)現(xiàn)對(duì)進(jìn)程的內(nèi)存擴(kuò)容荧止。
- break或者return來避免cprintf()以及myproc()->killed,這個(gè)意思是說省的調(diào)用cprintf輸出了錯(cuò)誤信息,所以應(yīng)該在錯(cuò)誤信息之前就返回
- 你應(yīng)該調(diào)用mappages().為了能夠調(diào)用mappages(),首先需要在vm.c中將mappages()前面的static移除跃巡,然后還要在trap.c當(dāng)中聲明一下牧愁。
- 你可以通過tf->trapno == T_PGFLT來判斷此時(shí)的錯(cuò)誤是不是page fault
- 使用PGROUNDDOWN(va)來講虛擬地址向下取整
本來按照題目的意思應(yīng)該是在default里面通過if(tf->trapno == T_PGFLT)
來判斷的,不過我這樣做了猪半,我加一個(gè)新的case來實(shí)現(xiàn)這個(gè),就直接給出代碼吧,思路就卸載注釋里面:
case T_PGFLT:
cprintf("page fault occured \n"); //測(cè)試一下我們進(jìn)入了這里的代碼
struct proc *curproc = myproc(); //獲得當(dāng)前進(jìn)程
uint addr = PGROUNDDOWN(rcr2()); //向下取整磨确,cr2寄存器保存這引發(fā)page fault的地址
char *mem = kalloc(); //新申請(qǐng)一個(gè)頁,返回的是虛擬地址
memset(mem, 0, PGSIZE); //將這個(gè)頁設(shè)置的內(nèi)容設(shè)置為0
//mappages摆舟,將新申請(qǐng)到的虛擬地址和mem對(duì)應(yīng) 物理地址映射起來
if (mappages(curproc->pgdir, (char *)addr, PGSIZE, V2P(mem), PTE_W | PTE_U) < 0)
{
//如果映射失敗,kill這個(gè)線程恨诱,并且釋放剛剛申請(qǐng)的內(nèi)存。
//這里只是將進(jìn)程標(biāo)記為kill
cprintf("out of memory \n");
curproc->killed = 1;
kfree(mem);
}
break;
下面是我的實(shí)驗(yàn)結(jié)果,不知道什么原因會(huì)輸出兩次page fault occured,也就是說發(fā)生了兩次page fault照宝,原因我也暫時(shí)還不知道。上面的代碼還是比較簡(jiǎn)陋的厕鹃,challenge 里面的東西都還沒有實(shí)現(xiàn)。