MIT6.828 JOS系統 lab2

來源:互聯網
上載者:User

MIT6.828 LAB2:http://pdos.csail.mit.edu/6.828/2014/labs/lab2/

LAB2裡面主要講的是系統的分頁過程,還有就是簡單的虛擬位址到物理地址的過程。關於系統分頁,在MIT6.828 虛擬位址轉化為物理地址——二級分頁:http://blog.csdn.net/fang92/article/details/47320747中有講。

下面主要是lab2的幾個exercise的解題過程。

1.第一個boot_alloc()函數:

static void *boot_alloc(uint32_t n){static char *nextfree;// virtual address of next byte of free memorychar *result;if (!nextfree) {extern char end[];<span style="white-space:pre"></span>//end points to the end of the kernel's bss segment:nextfree = ROUNDUP((char *) end, PGSIZE);}if(n==0)return nextfree;result = nextfree;nextfree += n;nextfree = ROUNDUP( (char*)nextfree, PGSIZE);return result;}
boot_alloc(unit32_t n)主要是申請n個位元組的地址空間,返回申請空間的首地址。如果n是0,則返回nextfree(未配置的空間的首地址)。其中,分配的地址是頁對齊的,即4K對齊。

這個函數,巧妙應用了未初始化的全域變數和靜態變數會被自動初始化為0這個特性。函數以首先定義靜態局部變數nextfree,它指向空閑記憶體空間的首地址。由於未初始化,所以變數自動初始化為0,所以首次調用boot_alloc()函數的時候,nextfree的值是0,會執行下面的語句:

if (!nextfree) {//end points to the end of the kernel's bss segmentnextfree = ROUNDUP((char *) end, PGSIZE);}


end指向的是bss段的末尾,通過調用objdump -h kernel 可以看程式的各個段的資訊。

從上面的圖片可以看出,在整個程式裡面,.bss段的位置是最下面的,注意.comment段是一些程式的注釋資訊,是不進記憶體的。所以end未初始化的全域變數,在.bss段中。所以end是指向的是整個記憶體的最後一個地址。(.bss段到底是怎麼回事)。

接下來的ROUNDUP就是一個宏定義,用來4K對齊的,最後會返回一個地址,指向end的下一個空閑頁的首地址。

所以在系統第一次調用boot_alloc()這個函數的時候,首先nextfree會被指向第一個空閑頁的首地址。接下來,根據輸入的n,來分配地址。如果n=0,則返回nextfree,否則分配n位元組的地址,返回分配地址的首地址。注意,整個過程中,需要4K對齊。

2.mem_init()函數

這個函數只需要補充一部分就可以了。主要是要為struct PageInfo的結構體的指標pages申請一定的地址空間。首先來看struct PageInfo的定義:

struct PageInfo { //Next page on the free list.struct PageInfo *pp_link;uint16_t pp_ref;}
這個結構體,主要是用來儲存記憶體中的所有物理頁面的資訊的。每一個PageInfo對應一個物理頁面。

pageInfo主要有兩個變數:

pp_link表示下一個空閑頁,如果pp_link=0,則表示這個頁面被分配了,否則,這個頁面未被分配,是空閑頁面。

pp_ref表示頁面被引用數,如果為0,表示是空閑頁。(這個變數類似於智能指標中指標的引用計數)。

補充的代碼比較簡單,就是位pages申請足夠的空間(npages的頁面),來存放這些結構體,並且用memset來初始化:

pages = boot_alloc(npages * sizeof (struct PageInfo));memset(pages, 0, npages*sizeof(struct PageInfo));


關於pages:

pages是用來管理實體記憶體頁的一個結構體。首先,pages是整個系統實體記憶體的第0頁,pages的值是一個虛擬位址。由於結構體是連續的,所以通過pages[i]-pages可以得到頁的編號i,在通過i<<12就可以得到pages[i]所對應的頁的實體記憶體,由於實現系統的實體記憶體和虛擬記憶體的轉換比較簡單,虛擬記憶體=實體記憶體+ 0xF0000000.所以通過pages這個結構體,在知道具體的物理頁時,就可以很容易得到物理頁對應的物理地址和虛擬位址,如圖。

3.page_init()

page_init()函數主要就是結構體pages的初始化,就是在系統剛剛開始運行時,對實體記憶體分配進行初始化操作,按照上面的提示一步一步寫就可以了,比較簡單。


voidpage_init(void){size_t i;for (i = 0; i < npages; i++) {if(i == 0){pages[i].pp_ref = 1;pages[i].pp_link = NULL;}else if(i>=1 && i<npages_basemem){pages[i].pp_ref = 0;pages[i].pp_link = page_free_list; page_free_list = &pages[i];}else if(i>=IOPHYSMEM/PGSIZE && i< EXTPHYSMEM/PGSIZE ){pages[i].pp_ref = 1;pages[i].pp_link = NULL;}else if( i >= EXTPHYSMEM / PGSIZE && i < ( (int)(boot_alloc(0)) - KERNBASE)/PGSIZE){pages[i].pp_ref = 1;pages[i].pp_link =NULL;}else{pages[i].pp_ref = 0;pages[i].pp_link = page_free_list;page_free_list = &pages[i];}}}

在這個初始化的函數裡面,有一點需要注意。整個pages的結構通過pp_link來區分頁面是否是空閑頁。對於非空閑頁,只需要把pp_ref置位,pp_link置0就可以了,非空閑頁不用通過什麼東西來讓他相互關聯。但是對於空閑頁,由於需要即時申請頁面,在非空閑頁釋放頁面之後,也需要把對應的頁面加入到空閑頁的結構中,所以空閑頁的結構設計很重要。空閑頁的結構設計要滿足:申請頁面時,能夠較快的得到空閑頁;2.釋放頁面時,能把頁面較快的插入。

在這個實驗系統中,採用了一個比較巧妙的結構,通過page_free_list這個變數來完成頁面的申請和調用。

首先,頁面的結構如下圖所示:

通過圖可以看到,整個空閑頁面的結構有點像一個倒置的鏈表,因為在空閑頁面的結構的建立過程中,是從下往上建立的,相當於在鏈表裡面,其前端節點是一個NULL指標。而使用過程中,整個結構類似於一個棧,採用先進後出的原則,page_free_list指向棧頂點。

在剛開始的時候,page_free_list =0,page_free_list表示系統的當前空閑頁面。 初始化時,插入第一個空閑頁表,把page_free_list賦值給空閑頁表的pp_link,然後更新page_free_list,使得其指向第一個空閑頁表(圖中page0), 依次操作,直到空閑頁表插入完畢。當free_page的時候,只需按照上面的過程,插入被釋放的空閑頁面就可以了。當系統申請空閑頁表時,只需給出page_free_list所指向的頁表就行,然後更新page_free_list.當page_free_list = NULL時,表示沒有空閑頁表。

總的來說,整個空閑頁面的使用更像是一個棧。在整個結構中,page_free_list 起著很關鍵的作用。

這種結構,對於較大單位空間的管理比較有效,即簡單又高效。如果把單位頁面的大小縮小,考慮極端情況,以16B為一個頁面,那麼這種頁面管理方法就不行了。可以看到,每個pageInfo裡面有兩個變數,一個位指標,一個為int, 即一個page的大小為8位元組,以8位元組來管理16位元組大小的頁面,到時候整個記憶體中,有50%都是pages結構體。浪費的空間非常巨大。而對於實驗中的4K大小的頁面,其空間利用率就非常高,整個pages占的空間大小大約為8/4000。

總的來說,這種管理方式和結構使用於大單位記憶體的管理。而對於堆記憶體的管理,由於堆的申請是以4或8位元組為單位的,相當於頁面大小位4B/8B,這種管理方法就不可行了。



4.page_alloc() 和 page_free()

page_alloc()是頁面申請函數,整個函數思路比較簡單,就是通過讀取和更新page_free_list來申請頁面。只要注意空閑頁面被申請完的情況就可以了。

struct PageInfo *page_alloc(int alloc_flags){if(page_free_list == NULL)return NULL;struct PageInfo* page = page_free_list;page_free_list = page->pp_link;page->pp_link = 0;if(alloc_flags & ALLOC_ZERO)memset(page2kva(page), 0, PGSIZE);return page;}

page_free就是釋放頁面,也比較簡單,只需要注意pp_ref和pp_link是否為0即可。

voidpage_free(struct PageInfo *pp){if(pp->pp_link != 0  || pp->pp_ref != 0)panic("page_free is not right");pp->pp_link = page_free_list;page_free_list = pp;return; }

Part 2: Virtual Memory 一開始,就讓人看分段和分頁的相關內容,還有關於段翻譯和頁翻譯的一些東西,在做下面的東西之前,一定要瞭解分頁的相關的東西,分段由於這裡暫時涉及的不多,可以以後在看: Look at chapters 5 and 6 of the Intel 80386 Reference Manual 地址: http://pdos.csail.mit.edu/6.828/2014/readings/i386/toc.htm( 分段內容,有時間詳細看看) 寫的簡單部落格: MIT6.828 虛擬位址轉化為物理地址——二級分頁: http://blog.csdn.net/fang92/article/details/47320747 在實驗中,指出了很重要的一點,一般來說,c語言的指標,它指向的地址是段的位移量,即OFFSET,其實不是虛擬位址,還需要加上段基址才可以得到那個指標變數的虛擬位址,然後經過頁翻譯,得到實際的物理地址。 下面有句話對補全實驗的函數很重要: From code executing on the CPU, once we're in protected mode (which we entered first thing in boot/boot.S), there's no way to directly use a linear or physical address. All memory references are interpreted as virtual addresses and translated by the MMU, which means all pointers in C are virtual addresses. 這句話就告訴我們,在程式裡面的所有地址,都是虛擬位址,系統會通過MMU來翻譯得到他的物理地址。也就是說,程式裡面的一切的地址都是虛擬位址。即使是物理地址,程式在調用的時候也會把他當成是虛擬位址,會轉化為物理地址。這一點要特別的注意,剛開始的時候,沒有注意,導致後面寫函數的時候有一些很嚴重的問題,還很難發現…… 在JOS裡面,在lab2裡面可以暫時不要管段翻譯,可以看成整個系統是一個段的,即其段基址是0,段範圍(limit)是0xffffffff.整個系統就被分成了一個段,所以系統可以看成是純分頁形式的。在分頁中,地址的轉化也比較簡單,JOS被設計成0-256M的物理地址映射到以0xf0000000開始的256M虛擬位址,所以從物理地址轉化虛擬位址比較方便簡單。 question: 這道題木就是要分清楚在JOS系統裡面,什麼是物理地址,什麼是虛擬位址。由於系統會經常要進行地址的相關運算,所以經常要進行強制轉化,把一個unsigned int型變數轉化為一個地址,或者相反,所以在程式裡面,需要對兩者進行區分。 上面題目的問題比較簡單,x應該是uintptr_t,在程式裡面,任何指標都是虛擬位址(段位移)。
下面的一個練習是頁面管理,主要是頁映射的管理。

總共5個函數:

1. pgdir_walk():返回va對應的二級頁表的地址(PTE)

2. boot_map_region: [va, va+size)映射到[pa, pa+size)

3.page_lookup:返回虛擬位址va對應的物理地址的頁面page

4.page_remove:對va和其對應的頁面取消映射

5. page_insert():把va映射到指定的物理頁表page



1. pgdir_walk():返回va對應的二級頁表的地址(PTE)


這個函數的主要作用是給定一個虛擬位址va和pgdir(page director table 的首地址), 返回va所對應的pte(page table entry)。當va對應的二級頁表存在時,只需要直接按照頁面翻譯的過程給出PTE的地址就可以了。但是,當va對應的二級頁表還沒有被建立的時候,就需要手動的申請頁面,並且建立頁面了。過程比較簡單,但是在最後返回PTE的地址的時候,需要返回PTE地址對應的虛擬位址,而不能直接把pte的物理地址給出。因為程式裡面只能執行虛擬位址,給出的物理地址也會被當成是虛擬位址,一般會引發段錯誤。

pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create){// Fill this function inint pdeIndex = (unsigned int)va >>22;if(pgdir[pdeIndex] == 0 && create == 0)return NULL;if(pgdir[pdeIndex] == 0){struct PageInfo* page = page_alloc(1);if(page == NULL)return NULL;page->pp_ref++;pte_t pgAddress = page2pa(page);pgAddress |= PTE_U;pgAddress |= PTE_P;pgAddress |= PTE_W;pgdir[pdeIndex] = pgAddress;}pte_t pgAdd = pgdir[pdeIndex];pgAdd = pgAdd>>12<<12;int pteIndex =(pte_t)va >>12 & 0x3ff;pte_t * pte =(pte_t*) pgAdd + pteIndex;return KADDR( (pte_t) pte );<span style="white-space:pre"></span>//一定要返回虛擬位址,物理地址會引發錯誤。}

2. boot_map_region: [va, va+size)映射到[pa, pa+size)

這個函數比較簡單,功能就是把 [va, va+size)映射到[pa, pa+size)

static voidboot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm){while(size){pte_t* pte = pgdir_walk(pgdir, (void* )va, 1);if(pte == NULL)return;*pte= pa |perm|PTE_P;size -= PGSIZE;pa  += PGSIZE;va  += PGSIZE;}}

3.page_lookup:返回虛擬位址va對應的物理地址的頁面page

struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store){pte_t* pte = pgdir_walk(pgdir, va, 0);if(pte == NULL)return NULL;pte_t pa =  *pte>>12<<12;if(pte_store != 0)*pte_store = pte ;return pa2page(pa);}

4.page_remove:對va和其對應的頁面取消映射

對va對應的頁面進行釋放,換句話說,就是取消va的映射。在這裡注意,進行頁面釋放之後,需要把va對應的PTE裡面儲存的值進行清零操作,否則查詢va對應的PTE時,會發生錯誤,系統會誤以為va和pa還是存在對應關係。

voidpage_remove(pde_t *pgdir, void *va){pte_t* pte;struct PageInfo* page = page_lookup(pgdir, va, &pte);if(page == 0)return;*pte = 0;page->pp_ref--;if(page->pp_ref ==0)page_free(page);tlb_invalidate(pgdir, va);}
這裡,

voidtlb_invalidate(pde_t *pgdir, void *va){// Flush the entry only if we're modifying the current address space.// For now, there is only one address space, so always invalidate.invlpg(va);}
關於TLB,可以看:http://blog.chinaunix.net/uid-16361381-id-3044981.htm

由於TLB是頁表條目的緩衝,由於在這裡,我們暫時沒有考慮系統中的多進程的問題。所以可以假設TLB中儲存的緩衝條目都是一個進程的,即地址空間是一個。所以當va進行取消映射的時候,需要檢查TLB中有無va的快取頁面麵條目,如果有,進行相應的操作。


5. page_insert():把va映射到指定的物理頁表page

這個函數要考慮三種情況:

1.va沒有對應的映射page

2:va有對應的映射page,但是不是指定的page

3.: va有對應的映射page,並且和指定的page相同。

對於情況1,最簡單,直接把va和page映射就可以了,具體方法就是把va對應的pte求出,然後修改pte裡面的值,使其為對應的page的物理地址。

對於情況2,先要把va對應的page釋放(remove,ref-1),然後和情況1一樣的處理方法。

對於情況3,要注意一點,當兩個page相同的時候,不能直接返回。因為page_insert函數不僅要進行虛擬位址和頁面的映射,它還要對頁面的特權(PTE_U,PTE_P,PTE_W)進行設定,如果原來的page的特權和現在的特權不一樣,那麼直接return,就會存在問題。

intpage_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm){pte_t* pte = pgdir_walk(pgdir, va, 1);if(pte == NULL)return -E_NO_MEM;if( (pte[0] &  ~0xfff) == page2pa(pp))pp->pp_ref--;else if(*pte != 0)page_remove(pgdir, va);*pte = (page2pa(pp) & ~0xfff) | perm | PTE_P;pp->pp_ref++;return 0;}


現在對part2部分的頁面管理做一個總結:


part2裡面的各種函數其實就是對上面的二級頁目錄做一個管理,主要就是va和pa的映射。

pgdir_walk()函數就是返回va所對應的二級頁表的pte的地址,在查詢一級頁目錄時,如果va對應的pde裡面沒有儲存對應的pte的物理基址,表明va還沒有進行地址映射。此時,根據creat,來決定是否需要進行二級頁面的建立。若需要,則申請一個閒置頁面,作為va的二級頁表,然後返回對應的二級頁表的地址(pte的地址)。

boot_map_region()函數就是向va對應的pte中填入對應的物理地址,以完成虛擬位址到物理地址的映射。

page_lookup()函數則是返回va對應的物理地址所在的頁面page。

page_remove()函數則是取消va和對應的page的映射,即是把va對應的pte中的內容清零,然後由於對應的page和va失去了映射,所以需要把pp_ref-1.

page_insert()函數則是把va和要求的page建立映射關係,就是向pte裡面填入page對應的物理地址。

綜上,總的來說,part2就是寫各種使用建立二級頁面的函數。

Part 3: Kernel Address Space part3主要是使用者空間和核心空間的一些東西。主要區分就是ULIM,在ULIM上面的就是核心空間,在下面的部分就是使用者空間。 如圖:


在part2中的一級頁目錄和二級頁表的地址的低12中,都有頁面的特權位。


上圖就是pte和pde中的地址分布情況,由於低12位是位移位,是由va決定的,所以低12位就被用作頁面的標誌位。這裡主要看低3位,即U,W,P三個標誌位。

p:代表頁面是否有效,若為1,表示頁面有效。否則,表示頁面無效,不能映射頁面,否則會發生錯誤。

W:表示頁面是否可寫。若為1,則頁面可以進行寫操作,否則,頁面是唯讀頁面,不能進行修改。

U:表示使用者程式是否可以使用該頁面。若位1,表示此頁面是使用者頁面,使用者程式可以使用並且訪問該頁面。若為0,則表示使用者程式不能訪問該頁面,只有核心才能訪問頁面。

上面的頁面標誌位,可以有效保護系統的安全。由於作業系統運行在核心空間(微核心除外,其部分系統功能在使用者態下進行)中運行,而一般的使用者程式都是在使用者空間上啟動並執行。所以使用者程式的奔潰,不會影響到作業系統,因為使用者程式無權對核心地址中的內容進行修改。這就有效對作業系統和使用者程式進行了隔離,加強了系統的穩定性。

在整個地址的中間的地址部分[UTOP,ULIM),使用者程式和核心都可以訪問,但是這部分記憶體是唯讀,不可以修改,在這部分記憶體中,主要儲存核心的一些唯讀資料。可能GDT什麼的一些表就存在這部分地址空間中。

接下去的低位記憶體中,存的就是給使用者程式使用的地址了。

剩下3個函數:

第一個:要求把pages結構體所在的頁面和虛擬位址UPAGES相互映射。這裡只要計算出pages結構體的大小,就可以通過page_insert()進行映射了。

<span style="white-space:pre"></span>int perm = PTE_U | PTE_P;int i=0; n = ROUNDUP(npages*sizeof(struct PageInfo), PGSIZE);for(i=0; i<n; i= i+PGSIZE)page_insert(kern_pgdir, pa2page(PADDR(pages) + i), (void *) (UPAGES +i), perm);


第二個:把虛擬位址[KSTACKTOP-KSTKSIZE, KSTACKTOP)映射到以bootstack為起點的物理地址(bootstack實際儲存的是其虛擬位址,需要轉換位物理地址),這是地址之間的靜態映射,所以用boot_map_region()就可以了

perm =0;perm = PTE_P |PTE_W;boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, ROUNDUP(KSTKSIZE, PGSIZE), PADDR(bootstack), perm);

第三個也是一個地址的靜態映射,是把地址從[KERNBASE, 2^32)映射到[0, 2^32 - KERNBASE)。這裡唯一的一點麻煩就是32位機子沒有辦法表示2^32,要通過一定的方法來得到size。

int size = ~0;size = size - KERNBASE +1;size = ROUNDUP(size, PGSIZE);perm = 0;perm = PTE_P | PTE_W;boot_map_region(kern_pgdir, KERNBASE, size, 0, perm );

整個lab2的基礎部分就完成了,感覺沒什麼東西,卻做了好久……

接下來是最後幾個question:

We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel's memory? What specific mechanisms protect the kernel memory?

上面說過,主要是靠PTE_U來保護。


What is the maximum amount of physical memory that this operating system can support? Why?

由於在記憶體中,UPAGES總共有4M的記憶體來儲存pages,也就是總共可以存4M/8Byte=0.5M個頁面,總共的記憶體大小為0.5M*4K=2G,所以總共2G記憶體最大。


How much space overhead is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

2G記憶體的話,總共頁面數就是0.5M個,pages結構體(PageInfo)的大小就是0.5M*8Byte=4M,page director是4K, 由於pages的數目為0.5M,所以page table是0.5M*4byge=2M,所以總共是6M+4k

一般來說,32位系統的話,能放得最大記憶體應該是4G的。這裡計算出來2G主要原因是UPAGES的記憶體範圍只有4M,如果UPAGES擴充到8M,那麼,這樣系統就可以極限定址到4G。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.