MIT-6.828 Lab 2: Memory Management實驗報告
tags:mit-6.828 os
概述
本文主要介紹lab2,講的是操作系統內存管理,從內容上分為三部分:
- 第一部分講的是物理內存管理,要進行內存管理首先需要知道哪些物理內存是空閑的,哪些是被使用的。還需要實現一些函數對這些物理內存進行管理。
- 第二部分講的是虛擬內存。一個虛擬地址如何被映射到物理地址,將實現一些函數來操作頁目錄和頁表從而達到映射的目的。
- 第三部分講的是內核的地址空間。將結合第一部分和第二部分的成果,來對內核地址空間進行映射。
Part 1: Physical Page Management
通過lab1可以總結出如下的物理內存分布圖:
大致上可以分為三部分:
- 0x00000~0xA0000:這部分叫做basemem,是可用的。
- 接着是0xA0000~0x100000:這部分叫做IO Hole,不可用。
- 再接着就是0x100000以上的部分:這部分叫做extmem,可用。
kern/pmap.c中的i386_detect_memory()統計有多少可用的物理內存,將總共的可用物理內存頁數保存到全局變量npages中,basemem部分可用的物理內存頁數保存到npages_basemem中。
Exercise 1:
需要我們寫一個物理內存頁的allocator。要求實現kern/pmap.c文件中的boot_alloc(),mem_init(),page_init(),page_alloc(),page_free()。check_page_free_list()和check_page_alloc()中會有一些測試用例,如果沒有通過兩個函數則說明代碼有問題,一種類似TDD的開發流程。
從lab1知道,進入內核后首先調用的是i386_init(),該函數會調用mem_init()。mem_init()調用其他工具函數實現內核內存管理。該函數首先調用i386_detect_memory()來計算有多少可以的物理內存頁保存到npages和npages_basemem中。然后調用boot_alloc()。
boot_alloc()實現如下:
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[]; //在/kern/kernel.ld中定義的符號,位於bss段的末尾
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
result = nextfree;
nextfree = ROUNDUP((char *)result + n, PGSIZE);
cprintf("boot_alloc memory at %x, next memory allocate at %x\n", result, nextfree);
return result;
}
該函數維護一個static的指針nextfree,初始值是end,end是定義在/kern/kernel.ld中定義的符號,位於bss段的末尾。也就是說從內核的末尾開始分配物理內存。需要添加的代碼是:
result = nextfree;
nextfree = ROUNDUP((char *)result + n, PGSIZE);
cprintf("boot_alloc memory at %x, next memory allocate at %x\n", result, nextfree);
return result;
每次調用都返回nextfree,然后根據參數n更新nextfree的值,使其指向下一個空閑地址處。
mem_init()調用boot_alloc(),將返回值賦給全局變量kern_pgdir,kern_pgdir保存的是內核頁目錄的物理地址。
接着根據mem_init()中的注釋:
// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
// The kernel uses this array to keep track of physical pages: for
// each physical page, there is a corresponding struct PageInfo in this
// array. 'npages' is the number of physical pages in memory. Use memset
// to initialize all fields of each struct PageInfo to 0.
在mem_init()中補充如下代碼:
pages = (struct PageInfo*)boot_alloc(sizeof(struct PageInfo) * npages); //分配足夠大的空間(PGSIZE的倍數)保存pages數組
memset(pages, 0, sizeof(struct PageInfo) * npages);
這段代碼分配足夠的的內存空間保存pages數組,pages數組的每一項是一個PageInfo結構,對應一個物理頁的信息,定義在inc/memlayout.h中。
接下來mem_init()調用page_init()。
page_init()實現如下:
// --------------------------------------------------------------
// Tracking of physical pages.
// The 'pages' array has one 'struct PageInfo' entry per physical page.
// Pages are reference counted, and free pages are kept on a linked list.
// --------------------------------------------------------------
//
// Initialize page structure and memory free list.
// After this is done, NEVER use boot_alloc again. ONLY use the page
// allocator functions below to allocate and deallocate physical
// memory via the page_free_list.
//
void
page_init(void)
{
// The example code here marks all physical pages as free.
// However this is not truly the case. What memory is free?
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don't, but...)
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
// 4) Then extended memory [EXTPHYSMEM, ...).
// Some of it is in use, some is free. Where is the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
//
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
// 這里初始化pages中的每一項,建立page_free_list鏈表
// 已使用的物理頁包括如下幾部分:
// 1)第一個物理頁是IDT所在,需要標識為已用
// 2)[IOPHYSMEM, EXTPHYSMEM)稱為IO hole的區域,需要標識為已用。
// 3)EXTPHYSMEM是內核加載的起始位置,終止位置可以由boot_alloc(0)給出(理由是boot_alloc()分配的內存是內核的最尾部),這塊區域也要標識
size_t i;
size_t io_hole_start_page = (size_t)IOPHYSMEM / PGSIZE;
size_t kernel_end_page = PADDR(boot_alloc(0)) / PGSIZE; //這里調了半天,boot_alloc返回的是虛擬地址,需要轉為物理地址
for (i = 0; i < npages; i++) {
if (i == 0) {
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
} else if (i >= io_hole_start_page && i < kernel_end_page) {
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數組,並且構建一個PageInfo鏈表,保存空閑的物理頁,表頭是全局變量page_free_list。具體實現看注釋。
接着實現page_alloc():
// Allocates a physical page. If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes. Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
//
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
//
// Returns NULL if out of free memory.
//
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
struct PageInfo *ret = page_free_list;
if (ret == NULL) {
cprintf("page_alloc: out of free memory\n");
return NULL;
}
page_free_list = ret->pp_link;
ret->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO) {
memset(page2kva(ret), 0, PGSIZE);
}
return ret;
}
該函數的作用是:從page_free_list指向的鏈表中取一個PageInfo結構返回,根據參數alloc_flags決定要不要將這塊內存初始化為0。需要注意的是,不需要增加PageInfo的pp_ref字段。
和page_alloc()對稱的是page_free()實現如下:
// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
void
page_free(struct PageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
if (pp->pp_ref != 0 || pp->pp_link != NULL) {
panic("page_free: pp->pp_ref is nonzero or pp->pp_link is not NULL\n");
}
pp->pp_link = page_free_list;
page_free_list = pp;
}
該函數重新將參數pp對應的物理頁設置為空閑狀態。
重新回到mem_init()的流程中來,在調用page_init()后,會調用check_page_free_list(1)和check_page_alloc()。這兩個函數通過一系列斷言,判斷我們的實現是否符合預期。需要注意的是check_page_free_list()中的這段代碼:
if (only_low_memory) {
// Move pages with lower addresses first in the free
// list, since entry_pgdir does not map all pages.
struct PageInfo *pp1, *pp2;
struct PageInfo **tp[2] = { &pp1, &pp2 };
for (pp = page_free_list; pp; pp = pp->pp_link) {
int pagetype = PDX(page2pa(pp)) >= pdx_limit;
*tp[pagetype] = pp;
tp[pagetype] = &pp->pp_link;
} //執行該for循環后,pp1指向(0~4M)中地址最大的那個頁的PageInfo結構。pp2指向所有頁中地址最大的那個PageInfo結構
*tp[1] = 0;
*tp[0] = pp2;
page_free_list = pp1;
}
剛開始也沒看明白,最后在紙上塗塗畫畫了半天才搞明白,關鍵是要理解for循環結束后pp1和pp2所指向的地址的具體含義。這段代碼的作用就是調整page_free_list鏈表的順序,將代表低地址的PageInfo結構放到鏈表的表頭處,這樣的話,每次分配物理地址時都是從低地址開始。
這樣第一部分就結束了,現在pages數組保存這所有物理頁的信息,page_free_list鏈表記錄這所有空閑的物理頁。可以用page_alloc()和page_free()進行分配和回收。
執行完mem_init()后的物理內存如下:
Part 2: Virtual Memory
這部分主要的目的是實現一些函數操作頁目錄和頁表從而達到實現虛擬地址到物理地址映射的目的。
Selector +--------------+ +-----------+
---------->| | | |
| Segmentation | | Paging |
Software | |-------->| |----------> RAM
Offset | Mechanism | | Mechanism |
---------->| | | |
+--------------+ +-----------+
Virtual Linear Physical
這張圖展示了x86體系中虛擬地址,線性地址,物理地址的轉換過程。在boot/boot.S中我們設置了全局描述符表(GDT),設置所有段的基地址都是0x0,所有虛擬地址的offset和線性地址都是相等的。
在lab1中已經安裝了一個簡易的頁目錄和頁表,將虛擬地址[0, 4MB)映射到物理地址[0, 4MB),[0xF0000000, 0xF0000000+4MB)映射到[0, 4MB)。具體實現在kern/entry.S中,臨時的頁目錄線性地址為entry_pgdir,定義在kern/entrypgdir.c中。
線性地址到物理地址的轉換過程可以用下圖表示:
通過這張圖我們知道頁目錄和和頁表實際上構成了一棵樹結構,任何映射關系我們都只需要修改這棵樹結構就能實現。
頁表條目結構如下:
每一位的具體含義可以參考Intel 80386 Reference Manual。
JOS內核有時候在僅知道物理地址的情況下,想要訪問該物理地址,但是沒有辦法繞過MMU的線性地址轉換機制,所以沒有辦法用物理地址直接訪問。JOS將虛擬地址0xf0000000映射到物理地址0x0處的一個原因就是希望能有一個簡便的方式實現物理地址和線性地址的轉換。在知道物理地址pa的情況下可以加0xf0000000得到對應的線性地址,可以用KADDR(pa)宏實現。在知道線性地址va的情況下減0xf0000000可以得到物理地址,可以用宏PADDR(va)實現。
Exercise 4
該實驗要求我們實現:
- pgdir_walk()
- boot_map_region()
- page_lookup()
- page_remove()
- page_insert()
pagedir_walk():
參數:
- pgdir:頁目錄虛擬地址
- va:虛擬地址
- create:布爾值
返回值:頁表條目的地址
作用:給定pgdir,指向一個頁目錄,該函數返回一個指針指向虛擬地址va對應的頁表條目(PTE)。
// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
// a pointer to the page table entry (PTE) for linear address 'va'.
// This requires walking the two-level page table structure.
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, then pgdir_walk returns NULL.
// Otherwise, pgdir_walk allocates a new page table page with page_alloc.
// - If the allocation fails, pgdir_walk returns NULL.
// - Otherwise, the new page's reference count is incremented,
// the page is cleared,
// and pgdir_walk returns a pointer into the new page table page.
//
// Hint 1: you can turn a PageInfo * into the physical address of the
// page it refers to with page2pa() from kern/pmap.h.
//
// Hint 2: the x86 MMU checks permission bits in both the page directory
// and the page table, so it's safe to leave permissions in the page
// directory more permissive than strictly necessary.
//
// Hint 3: look at inc/mmu.h for useful macros that manipulate page
// table and page directory entries.
//
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
pde_t* pde_ptr = pgdir + PDX(va);
if (!(*pde_ptr & PTE_P)) { //頁表還沒有分配
if (create) {
//分配一個頁作為頁表
struct PageInfo *pp = page_alloc(1);
if (pp == NULL) {
return NULL;
}
pp->pp_ref++;
*pde_ptr = (page2pa(pp)) | PTE_P | PTE_U | PTE_W; //更新頁目錄項
} else {
return NULL;
}
}
return (pte_t *)KADDR(PTE_ADDR(*pde_ptr)) + PTX(va); //這里記得轉為pte_t*類型,因為KADDR返回的的是void*類型。調了一個多小時才發現
}
結合圖二的頁轉換的過程很容易理解,需要注意一點,最后返回的的時候KADDR(PTE_ADDR(pde_ptr))返回的void **類型如果直接加上PTX(va)是不對的,應該先轉為(pte_t),再做加法運算,這個bug調了一個多小時才發現( ╯□╰ )。
boot_map_region()
參數:
- pgdir:頁目錄指針
- va:虛擬地址
- size:大小
- pa:物理地址
- perm:權限
作用:通過修改pgdir指向的樹,將[va, va+size)對應的虛擬地址空間映射到物理地址空間[pa, pa+size)。va和pa都是頁對齊的。
// Map [va, va+size) of virtual address space to physical [pa, pa+size)
// in the page table rooted at pgdir. Size is a multiple of PGSIZE, and
// va and pa are both page-aligned.
// Use permission bits perm|PTE_P for the entries.
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//
// Hint: the TA solution uses pgdir_walk
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
size_t pgs = size / PGSIZE;
if (size % PGSIZE != 0) {
pgs++;
} //計算總共有多少頁
for (int i = 0; i < pgs; i++) {
pte_t *pte = pgdir_walk(pgdir, (void *)va, 1);//獲取va對應的PTE的地址
if (pte == NULL) {
panic("boot_map_region(): out of memory\n");
}
*pte = pa | PTE_P | perm; //修改va對應的PTE的值
pa += PGSIZE; //更新pa和va,進行下一輪循環
va += PGSIZE;
}
}
思路很簡單,看注釋即可。
page_insert()
參數:
- pgdir:頁目錄指針
- pp:PageInfo結構指針,代表一個物理頁
- va:線性地址
- perm:權限
返回值:0代表成功,-E_NO_MEM代表物理空間不足。
作用:修改pgdir對應的樹結構,使va映射到pp對應的物理頁處。
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table entry
// should be set to 'perm|PTE_P'.
//
// Requirements
// - If there is already a page mapped at 'va', it should be page_remove()d.
// - If necessary, on demand, a page table should be allocated and inserted
// into 'pgdir'.
// - pp->pp_ref should be incremented if the insertion succeeds.
// - The TLB must be invalidated if a page was formerly present at 'va'.
//
// Corner-case hint: Make sure to consider what happens when the same
// pp is re-inserted at the same virtual address in the same pgdir.
// However, try not to distinguish this case in your code, as this
// frequently leads to subtle bugs; there's an elegant way to handle
// everything in one code path.
//
// RETURNS:
// 0 on success
// -E_NO_MEM, if page table couldn't be allocated
//
// Hint: The TA solution is implemented using pgdir_walk, page_remove,
// and page2pa.
//
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
// Fill this function in
pte_t *pte = pgdir_walk(pgdir, va, 1); //拿到va對應的PTE地址,如果va對應的頁表還沒有分配,則分配一個物理頁作為頁表
if (pte == NULL) {
return -E_NO_MEM;
}
pp->pp_ref++; //引用加1
if ((*pte) & PTE_P) { //當前虛擬地址va已經被映射過,需要先釋放
page_remove(pgdir, va); //這個函數目前還沒實現
}
physaddr_t pa = page2pa(pp); //將PageInfo結構轉換為對應物理頁的首地址
*pte = pa | perm | PTE_P; //修改PTE
pgdir[PDX(va)] |= perm;
return 0;
}
page_lookup()
參數:
- pgdir:頁目錄地址
- va:虛擬地址
- pte_store:一個指針類型,指向pte_t *類型的變量
返回值:PageInfo*
作用:通過查找pgdir指向的樹結構,返回va對應的PTE所指向的物理地址對應的PageInfo結構地址。
// Return the page mapped at virtual address 'va'.
// If pte_store is not zero, then we store in it the address
// of the pte for this page. This is used by page_remove and
// can be used to verify page permissions for syscall arguments,
// but should not be used by most callers.
//
// Return NULL if there is no page mapped at va.
//
// Hint: the TA solution uses pgdir_walk and pa2page.
//
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in
struct PageInfo *pp;
pte_t *pte = pgdir_walk(pgdir, va, 0); //如果對應的頁表不存在,不進行創建
if (pte == NULL) {
return NULL;
}
if (!(*pte) & PTE_P) {
return NULL;
}
physaddr_t pa = PTE_ADDR(*pte); //va對應的物理
pp = pa2page(pa); //物理地址對應的PageInfo結構地址
if (pte_store != NULL) {
*pte_store = pte;
}
return pp;
}
如果將page_insert()函數看作是“增”,那么page_lookup()就是“查”。查找一個虛擬地址va,對應的物理地址信息。
page_remve()
參數:
- pgdir:頁目錄地址
- va:虛擬地址
作用:修改pgdir指向的樹結構,解除va的映射關系。
// Unmaps the physical page at virtual address 'va'.
// If there is no physical page at that address, silently does nothing.
//
// Details:
// - The ref count on the physical page should decrement.
// - The physical page should be freed if the refcount reaches 0.
// - The pg table entry corresponding to 'va' should be set to 0.
// (if such a PTE exists)
// - The TLB must be invalidated if you remove an entry from
// the page table.
//
// Hint: The TA solution is implemented using page_lookup,
// tlb_invalidate, and page_decref.
//
void
page_remove(pde_t *pgdir, void *va)
{
// Fill this function in
pte_t *pte_store;
struct PageInfo *pp = page_lookup(pgdir, va, &pte_store); //獲取va對應的PTE的地址以及pp結構
if (pp == NULL) { //va可能還沒有映射,那就什么都不用做
return;
}
page_decref(pp); //將pp->pp_ref減1,如果pp->pp_ref為0,需要釋放該PageInfo結構(將其放入page_free_list鏈表中)
*pte_store = 0; //將PTE清空
tlb_invalidate(pgdir, va); //失效化TLB緩存
}
如果將page_insert()函數看作是“增”,page_lookup()是“查”,那么page_remove()就是“刪”,刪除線性地址va的映射關系,刪除過后不可使用該虛擬地址,否則會出現頁錯誤,lab3將處理該錯誤。
至此如果一切順利,將通過mem_init()中check_page()的所有assert。
Part 3: Kernel Address Space
JOS將線性地址空間分為兩部分,由定義在inc/memlayout.h中的ULIM分割。ULIM以上的部分用戶沒有權限訪問,內核有讀寫權限。
/*
* Virtual memory map: Permissions
* kernel/user
*
* 4 Gig --------> +------------------------------+
* | | RW/--
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* : . :
* : . :
* : . :
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
* | | RW/--
* | Remapped Physical Memory | RW/--
* | | RW/--
* KERNBASE, ----> +------------------------------+ 0xf0000000 --+
* KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| |
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* | CPU1's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| PTSIZE
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* : . : |
* : . : |
* MMIOLIM ------> +------------------------------+ 0xefc00000 --+
* | Memory-mapped I/O | RW/-- PTSIZE
* ULIM, MMIOBASE --> +------------------------------+ 0xef800000
* | Cur. Page Table (User R-) | R-/R- PTSIZE
* UVPT ----> +------------------------------+ 0xef400000
* | RO PAGES | R-/R- PTSIZE
* UPAGES ----> +------------------------------+ 0xef000000
* | RO ENVS | R-/R- PTSIZE
* UTOP,UENVS ------> +------------------------------+ 0xeec00000
* UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebff000
* | Empty Memory (*) | --/-- PGSIZE
* USTACKTOP ---> +------------------------------+ 0xeebfe000
* | Normal User Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebfd000
* | |
* | |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* . .
* . .
* . .
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
* | Program Data & Heap |
* UTEXT --------> +------------------------------+ 0x00800000
* PFTEMP -------> | Empty Memory (*) | PTSIZE
* | |
* UTEMP --------> +------------------------------+ 0x00400000 --+
* | Empty Memory (*) | |
* | - - - - - - - - - - - - - - -| |
* | User STAB Data (optional) | PTSIZE
* USTABDATA ----> +------------------------------+ 0x00200000 |
* | Empty Memory (*) | |
* 0 ------------> +------------------------------+ --+
*
* (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
* "Empty Memory" is normally unmapped, but user programs may map pages
* there if desired. JOS user programs map pages temporarily at UTEMP.
*/
Exercise 5
該實驗需要我們填充mem_init()中缺失的代碼,使用part2的增刪改函數初始化內核線性地址空間。
在mem_init()函數的check_page();
后添加如下語句:
//////////////////////////////////////////////////////////////////////
// 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
// Your code goes here:
// 將虛擬地址的UPAGES映射到物理地址pages數組開始的位置
boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);
//////////////////////////////////////////////////////////////////////
// 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
// Your code goes here:
// 'bootstack'定義在/kernel/entry.
boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);
//////////////////////////////////////////////////////////////////////
// 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
// Your code goes here:
boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);
執行完mem_init()后kern_pgdir指向的內核頁目錄代表的虛擬地址空間到物理地址空間映射可以用下圖來表示:
如何仔細看圖和上面的代碼,會覺得奇怪,UVPT開始的這一頁是什么時候映射的?實際上早在mem_init()開始的時候就有這么一句kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
,頁目錄表的低PDX(UVPT)項指向頁目錄本身,也就是說虛擬地址UVPT開始處的0x400000字節映射到物理地址PADDR(kern_pgdir)處。
總結
至此,lab2的所有實驗都已完成。如果順利運行./grade-lab2會看到:
該實驗大體上做三件事:
- 提供管理物理內存的數據結構和函數,可總結為下圖:
- 提供修改頁目錄和頁表樹結構的函數,從而達到虛擬頁到物理頁映射的目的。可總結為下圖:
- 用前面兩部分的函數建立內核的線性地址空間。內核的線性地址空間到物理內存的映射可總結為下圖:
現在我們可以直接使用UPAGES這個虛擬地址直接訪問pages數組,使用UVPT訪問內核頁目錄表,使用KSTACKTOP訪問內核棧。
具體代碼在:https://github.com/gatsbyd/mit_6.828_jos
如有錯誤,歡迎指正:
15313676365