環(huán)境
ubuntu 20.04 64位系統(tǒng)
lab2地址:點(diǎn)擊我查看lab2
正文
lab2說(shuō)實(shí)話還是挺難的,這也首先歸因我對(duì)于頁(yè)式內(nèi)存了解有點(diǎn)不到位苗胀。在實(shí)驗(yàn)中走了一些彎路撮弧。不過(guò)還是很努力地把lab3的內(nèi)容都做完了。廢話不多說(shuō)线婚,開始干!
下面這段還是翻譯下lab2 part3的一些原文盆均。
JOS 將32bit的線性地址空間劃分為兩個(gè)部分塞弊。用戶使用低地址的部分,內(nèi)核使用高地址的部分。這兩者劃分的界限是inc/memlayout.h中的ULIM這個(gè)宏游沿,為內(nèi)核保留了大約256MB左右的虛擬地址空間饰抒。(之所以這里說(shuō)的是大約,是因?yàn)檫€有一部分是內(nèi)核的棧诀黍,還有一個(gè)Memory-mapped I/O袋坑,這一塊估計(jì)是給IO用的)。這也就是解釋了在lab1中提到的眯勾,為什么內(nèi)核占據(jù)了虛擬地址的高地址部分枣宫,因?yàn)槲覀円艚ouser process足夠的空間。
在inc/memlayou.h當(dāng)中有內(nèi)存的示意圖吃环,代碼里面的那個(gè)有點(diǎn)難懂也颤,我自己做一個(gè)在補(bǔ)充到這里。
幾個(gè)關(guān)鍵的位置我已經(jīng)用紅色標(biāo)出來(lái)了,UTOP下面的沒(méi)畫模叙,位置不夠了歇拆。不夠?qū)τ诒敬蝜ab已經(jīng)足夠了。
對(duì)于大于ULIM的地址范咨,用戶程序不能對(duì)這塊內(nèi)存執(zhí)行任何操作故觅,讀寫都不行,內(nèi)核對(duì)這一塊內(nèi)存可讀可寫渠啊。對(duì)于內(nèi)存從UTOP-ULIM,這一塊內(nèi)存對(duì)于kernel和user是read-only的输吏,不能修改。低于UTOP的地址是給user process用的替蛉。綠色的是我們本次實(shí)驗(yàn)需要關(guān)注的贯溅,page table不需要,只是感覺(jué)挺重要的躲查。細(xì)說(shuō)一下page table它浅。
Exercise 5
補(bǔ)充在mem_init()在chech_page()之后缺失的代碼
接下來(lái)你要設(shè)置的是大于UTOP的地址,內(nèi)存的結(jié)構(gòu)已經(jīng)在inc/memlayout.h當(dāng)中了镣煮,你要用lab2當(dāng)中的所實(shí)現(xiàn)的代碼來(lái)完成這些工作姐霍,這里說(shuō)明我們要操作的都是內(nèi)核能訪問(wèn)但是用戶不能訪問(wèn)的地址(除了第一題)。
第一道題
// Map 'pages' read-only by the user at linear address UPAGES
// Permissions:
// - the new image at UPAGES -- kernel R, user R
// (ie. perm = PTE_U | PTE_P)
// - pages itself -- kernel RW, user NONE
將pages的地址映射到UPAGES起始的虛擬地址典唇,這一塊對(duì)于用戶和kernel來(lái)說(shuō)都是read-only的镊折。pages是一個(gè)PageInfo結(jié)構(gòu)體數(shù)組存。在part2的當(dāng)中有一個(gè)函數(shù)叫做boot_map_region它能夠?qū)⒁粔K虛擬地址映射到一塊物理地址介衔。 看一下inc/memlayout.h中的地質(zhì)結(jié)構(gòu)恨胚,可以看到pages所占的大小是PTSIZE(一個(gè)page table可以索引的地址范圍,1024*4kb = 4MB)炎咖。這塊內(nèi)存的權(quán)限是PTE_U | PTE_P(P:是否在內(nèi)存當(dāng)中,U:If U is set,then the page accessed by all)赃泡。答案很明了了:
boot_map_region(kern_pgdir,UPAGES,PTSIZE,PADDR(pages),PTE_U | PTE_P);
將UPAGES起始的PTSIZE大小的內(nèi)存映射到物理地址為:PADDR(pages)的區(qū)域寒波,權(quán)限位PTE_U | PTE_P(只讀)
PS:
lab2頁(yè)面說(shuō)[UTOP-ULIM]這一塊內(nèi)存都是只讀的,我就想既然都是read only那么kernel怎么去修改pages這個(gè)結(jié)構(gòu)升熊,怎么去修改頁(yè)表呢?后來(lái)想想相通了(也不知道理解的對(duì)不對(duì))影所。首先我們要注意的是pages這些數(shù)據(jù)結(jié)構(gòu),實(shí)際上是在內(nèi)核的空間內(nèi)的(畢竟他們都是代碼里面的變量僚碎,一開始就被加載到內(nèi)存去了)。內(nèi)核可以對(duì)自己的變量操作阴幌,這是很顯而易見(jiàn)的勺阐。然后我們將UPAGES映射到pgaes的物理地址去。并且設(shè)置為read only矛双。對(duì)于kernel和user來(lái)說(shuō)渊抽,當(dāng)他們使用UPGAES來(lái)訪問(wèn)的時(shí)候,都是read only的议忽。但是內(nèi)核可以在自己的代碼里面自由地操作pages懒闷。(因?yàn)槲覀兇龝?huì)會(huì)對(duì)整個(gè)kernel映射到0-256MB這個(gè)地方,也就是說(shuō)有UPAGES可以訪問(wèn)pages栈幸,內(nèi)核自身的pages這個(gè)指針也可以訪問(wèn)Pages愤估。)懂了嗎老鐵?下面是用qemu-monitor 打印的結(jié)果(后期在補(bǔ)充一個(gè)qemu monitor 的使用):
可以看到內(nèi)存中的數(shù)據(jù)是相同的速址。這也說(shuō)明我們對(duì)pages映射成功了玩焰!
同樣的,頁(yè)表也是類似芍锚。首先先明白一個(gè)很重要的事實(shí)昔园,就是page directory也是也是page table。想一下什么是page table? 一個(gè)page table中的一個(gè)entry都對(duì)應(yīng)著一個(gè)頁(yè)的物理地址并炮。page directory一個(gè)entry對(duì)應(yīng)的一個(gè)page tbale, page table也是一個(gè)頁(yè)默刚。那我們很自然也可以將page directory看作是一個(gè)page table。下面來(lái)看關(guān)鍵的幾行代碼:
kern_pgdir = (pde_t*) boot_alloc(PGSIZE);
memset(kern_pgdir,0,PGSIZE);
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
首先先申請(qǐng)了一個(gè)PGSIZE大下的內(nèi)存用于存放page directory逃魄。然后將這塊內(nèi)存內(nèi)容都設(shè)置為0荤西。然后獲得虛擬地址UVPT對(duì)應(yīng)的pgae directory index,把對(duì)應(yīng)的page directory entry設(shè)置為PADDR(kern_pgdir) | PTE_U | PTE_P;
。也就是說(shuō)PDX(UVPT)
指向的是page directory自身嗅钻。關(guān)鍵記住把page directory當(dāng)作一個(gè)page table皂冰。一個(gè)Page table可以管理4MB的內(nèi)存,當(dāng)我們?cè)L問(wèn)UVPT到UVPT+PTSZIE這里面的內(nèi)存的時(shí)候养篓,都是訪問(wèn)由Page directory它管理的內(nèi)存(下圖是一個(gè)例子)秃流。但是page directory所管理的內(nèi)存是所有的page table.所以UVPT+PTSIZE可以看作是page table的內(nèi)存,懂了沒(méi)有 鐵汁柳弄!訪問(wèn)UVPT到UVPT+PTSZIE就是訪問(wèn)Page table!!這一段理解了好久才想明白舶胀,虛擬內(nèi)存還有一個(gè)很重要的特點(diǎn)就是概说,數(shù)據(jù)在虛擬內(nèi)存上表現(xiàn)是連續(xù)的,但是在實(shí)際的物理內(nèi)存當(dāng)中可能是非連續(xù)的也可能是連續(xù)的嚣伐,取決于頁(yè)被映射到了哪里糖赔。
如果我們直接訪問(wèn)UVPT:0xrf40_0000這個(gè)地址。我們來(lái)過(guò)一下這個(gè)過(guò)程:
- 首先計(jì)算得到PDE index=957,然后發(fā)現(xiàn)里面的地址就是kern_dir轩端。
- 然后page directory被作為Page table放典。PTE index =0,就取得第一個(gè)頁(yè)的的地址 page addr基茵。
- 最后加上offset奋构,形成了真正的地址。
其他的地址過(guò)程相類似拱层,各位可以試著試一下弥臼。相比能理解把page directoy當(dāng)做page table的意思。
上述的分析是我個(gè)人的理解根灯,我沒(méi)有通過(guò)debug去認(rèn)真的試驗(yàn)過(guò)!!
第二道題
// Use the physical memory that 'bootstack' refers to as the kernel
// stack. The kernel stack grows down from virtual address KSTACKTOP.
// We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
// to be the kernel stack, but break this into two pieces:
// * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
// * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
// the kernel overflows its stack, it will fault rather than
// overwrite memory. Known as a "guard page".
// Permissions: kernel RW, user NONE
這一道題完成的是將內(nèi)核棧映射到物理地址径缅。看一下上面內(nèi)存的結(jié)構(gòu)圖烙肺,內(nèi)核棧的總大小是[KSTACKTOP-PTSIZE, KSTACKTOP)纳猪。但是上面題目說(shuō),這一塊棧的內(nèi)存要分為兩塊大小. [KSTACKTOP-KSTKSIZE, KSTACKTOP) 這一塊地方是需要被映射的茬高,[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) 這一塊地方不需要被映射兆旬。也就是說(shuō)當(dāng)我們的棧超過(guò)了[KSTACKTOP-KSTKSIZE, KSTACKTOP)會(huì)報(bào)棧溢出了,但是溢出的數(shù)據(jù)會(huì)到[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) 這里怎栽。并不會(huì)消失丽猬。上面一道題明白后,這道題就簡(jiǎn)單了熏瞄。注意我們?cè)趦?nèi)核中棧的地址是在bootstack,它此時(shí)也是虛擬地址脚祟,所以首先要將它轉(zhuǎn)為物理地址。然后在映射强饮,下面是答案:
boot_map_region(kern_pgdir,KSTACKTOP-KSTKSIZE,KSTKSIZE,PADDR(bootstack),PTE_W | PTE_P);
從KSTACKTOP-KSTKSIZE映射一個(gè)KSTKSIZE大小的內(nèi)存到PADDR(bootstack)這里去由桌,權(quán)限是PTE_W | PTE_P(可讀寫,并且頁(yè)都在內(nèi)存當(dāng)中)邮丰,PTE_U沒(méi)有設(shè)置行您,所以只有內(nèi)核可以訪問(wèn)它。
第三題
// Map all of physical memory at KERNBASE.
// Ie. the VA range [KERNBASE, 2^32) should map to
// the PA range [0, 2^32 - KERNBASE)
// We might not have 2^32 - KERNBASE bytes of physical memory, but
// we just set up the mapping anyway.
// Permissions: kernel RW, user NONE
將所有的內(nèi)核地址映射到[0, 2^32 - KERNBASE),這個(gè)真的太簡(jiǎn)單了剪廉,沒(méi)什么好說(shuō)的娃循,直接上答案。內(nèi)核占據(jù)的大小是256MB斗蒋,知道這點(diǎn)就好啦
boot_map_region(kern_pgdir,KERNBASE,0xffffffff-KERNBASE,0,PTE_W | PTE_P);
question
-
到目前為止捌斧,page directory當(dāng)中那些已經(jīng)被填充了笛质?他們又分別指向哪里呢?(logically 捞蚂,雖然page directory當(dāng)中應(yīng)該存放的是物理地址妇押,但是題目要求我們是寫虛擬地址)
page directory
當(dāng)我們?cè)L問(wèn)logical里面的地址的時(shí)候,在page directory當(dāng)中會(huì)映射到phyiscal當(dāng)中去姓迅。
- 為什么在同一個(gè)地址空間當(dāng)中敲霍,用戶程序不能訪問(wèn)內(nèi)核的內(nèi)存的呢?
這個(gè)問(wèn)題比較簡(jiǎn)單了丁存,因?yàn)槲覀儗?duì)內(nèi)核的內(nèi)存頁(yè)設(shè)置pte_w,但沒(méi)有設(shè)置PTE_U色冀。所以有些內(nèi)存只能內(nèi)核自己訪問(wèn),而用戶無(wú)法訪問(wèn)柱嫌。 - 該操作系統(tǒng)可以管理的內(nèi)存最大值是多少?
每一個(gè)頁(yè)都以可以對(duì)應(yīng)的PageInfo屯换,這個(gè)結(jié)構(gòu)的有8字節(jié)编丘。8字節(jié)計(jì)算方法:指針+short=6字節(jié),但是gcc會(huì)在后面追加兩個(gè)2字節(jié)的空數(shù)據(jù)彤悔,這樣就在32bit上的電腦完成了字節(jié)對(duì)齊嘉抓。看一下內(nèi)存結(jié)構(gòu)圖,pages這個(gè)數(shù)據(jù)結(jié)構(gòu)分配的大小是一個(gè)PTSIZE晕窑。4MB/8 bit = 512K個(gè)抑片,也就是說(shuō)我們的系統(tǒng)可以管理512K個(gè)物理頁(yè)。512K*4KB=2GB杨赤。所以這個(gè)系統(tǒng)總共可以管理2GB的內(nèi)存敞斋。 - 管理內(nèi)存的開銷是多少,如果我們使用了所有的虛擬內(nèi)存(在32bit的機(jī)器下是4GB)疾牲,那么需要多少內(nèi)存植捎?
回想一下,管理內(nèi)存所使用的主要就是page table+page directory+PageInfo阳柔。逐個(gè)計(jì)算他們的大小焰枢,page directory很簡(jiǎn)單,page directory:4KB舌剂。 每個(gè)page directory entry又對(duì)應(yīng)一個(gè)page table, page table =4KB济锄。 page table有1024個(gè),所以page table:1024 * 4KB=4MB霍转。 一個(gè)page對(duì)應(yīng)一個(gè)PageInfo,所以4GB對(duì)應(yīng)1M個(gè)page荐绝。1M * 8bit=8mb。
總共需要的內(nèi)存: 4kb+ 8mb+4mb=12mb+4kb - 重新回顧下kern/entry.S 和 kern/entrypgdir.c谴忧。當(dāng)我們開啟Paging的時(shí)候很泊,此時(shí)EIP還是小于1MB的內(nèi)存空間內(nèi)角虫。什么時(shí)候我們過(guò)渡到KERNBASE上面的內(nèi)存去呢?為什么能夠在打開頁(yè)表的時(shí)候仍然運(yùn)行在low EIP委造?我們什么時(shí)候運(yùn)行在大于在KERNBASE戳鹅?為什么這個(gè)過(guò)渡十分重要?
- 何時(shí)過(guò)渡到KERNBASE上面的內(nèi)存去昏兆?
mov $relocated, %eax
jmp *%eax
主要是這兩句枫虏,在開啟paging后,$relocated這個(gè)標(biāo)號(hào)對(duì)應(yīng)的地址已經(jīng)是大于KERNBASE了,然后jmp就跳轉(zhuǎn)到大于KERNBASE的內(nèi)容去了爬虱。也就是relocated標(biāo)號(hào)對(duì)應(yīng)的指令隶债。
-
為什么在開啟paging后,仍然能在low EIP的地方運(yùn)行指令跑筝?
一開始沒(méi)理解這里的意思死讹,后面明白了,在上面jmp還沒(méi)跳轉(zhuǎn)前曲梗。此時(shí)的指令還在內(nèi)存的低端赞警,但是因?yàn)闃?biāo)號(hào)的地址是和鏈接的地址有關(guān)的,$relocated已經(jīng)是大于KERNBASE的地址了虏两。如下圖所示:
image
可以看到$relocated對(duì)應(yīng)地址是0xf010_002f愧旦。不過(guò)呢此時(shí),mov和jmp都還在0x10020這樣的低地址處定罢◇猿妫回想下,我們已經(jīng)通過(guò) 改變cr0寄存器開啟了paging祖凫。為什么此時(shí)還可以運(yùn)行?講道理原來(lái)地址已經(jīng)不是簡(jiǎn)單的物理地址了琼蚯。答案就是這兩條語(yǔ)句:
movl $(RELOC(entry_pgdir)), %eax
movl %eax, %cr3
我們先使用entry_pgdir這個(gè)地址作為page directory的地址。具體細(xì)節(jié)看entrypgdir.c中的注釋惠况。之所以能繼續(xù)運(yùn)行凌停,是因?yàn)槲覀儗⑻摂M地址的[0, 4MB) 映射到了物理地址[0, 4MB)。注意這里沒(méi)有初始化所有page table,只初始化了第一個(gè)page table售滤。所以jmp和mov指令前面的地址雖然是虛擬地址罚拟,但是被映射到了相同的物理地址上,所以原來(lái)的代碼還是可以運(yùn)行的完箩。還有我們還將[KERNBASE, KERNBASE+4MB) 也映射到了物理地址的[0, 4MB)。當(dāng)我們正式進(jìn)入內(nèi)核的代碼弊知,比如說(shuō)在運(yùn)行mem_init()的時(shí)候秩彤,實(shí)際上的物理地址還是在物理地址[0, 4MB)這一塊內(nèi),當(dāng)我們把page_init()等這些函數(shù)實(shí)現(xiàn)以后,就可以實(shí)現(xiàn)真正的頁(yè)式內(nèi)存了谤辜。
- 什么時(shí)候運(yùn)行在大于KERNBASE的內(nèi)存呢丑念?
答案也很明顯了脯倚,jmp指令跳轉(zhuǎn)后就運(yùn)行在大于KERBASE的地址了,雖然實(shí)際上映射的地址還是在物理地址的[0, 4MB)∪榉幔看上面的截圖产园,左邊的地址已經(jīng)在0xf01002f了。
- 為什么這個(gè)過(guò)渡很重要?
因?yàn)槲覀兇藭r(shí)還沒(méi)有設(shè)置能夠管理頁(yè)式內(nèi)存的函數(shù)粘勒,所以我們只能先稍微將一小部分虛擬內(nèi)存映射屎即。在這一小部分內(nèi)存里面實(shí)現(xiàn)管理內(nèi)存的函數(shù)技俐。
PS
- 內(nèi)核的鏈接地址是在kern/kernel.ld中寫的雕擂。