1. 程式人生 > >MIT-6.828 Lab2實驗報告

MIT-6.828 Lab2實驗報告

MIT-6.828 Lab 2: Memory Management實驗報告

tags:mit-6.828 os

概述

本文主要介紹lab2,講的是作業系統記憶體管理,從內容上分為三部分:

  1. 第一部分講的是實體記憶體管理,要進行記憶體管理首先需要知道哪些實體記憶體是空閒的,哪些是被使用的。還需要實現一些函式對這些實體記憶體進行管理。
  2. 第二部分講的是虛擬記憶體。一個虛擬地址如何被對映到實體地址,將實現一些函式來操作頁目錄和頁表從而達到對映的目的。
  3. 第三部分講的是核心的地址空間。將結合第一部分和第二部分的成果,來對核心地址空間進行對映。

Part 1: Physical Page Management

通過lab1可以總結出如下的實體記憶體分佈圖: 實體記憶體分佈 大致上可以分為三部分:

  1. 0x00000~0xA0000:這部分叫做basemem,是可用的。
  2. 接著是0xA0000~0x100000:這部分叫做IO Hole,不可用。
  3. 再接著就是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()後的實體記憶體如下: 執行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中。 線性地址到實體地址的轉換過程可以用下圖表示: MMU線性地址轉換過程 通過這張圖我們知道頁目錄和和頁表實際上構成了一棵樹結構,任何對映關係我們都只需要修改這棵樹結構就能實現。 頁表條目結構如下: 頁表條目結構 每一位的具體含義可以參考Intel 80386 Reference Manual。 JOS核心有時候在僅知道實體地址的情況下,想要訪問該實體地址,但是沒有辦法繞過MMU的線性地址轉換機制,所以沒有辦法用實體地址直接訪問。JOS將虛擬地址0xf0000000對映到實體地址0x0處的一個原因就是希望能有一個簡便的方式實現實體地址和線性地址的轉換。在知道實體地址pa的情況下可以加0xf0000000得到對應的線性地址,可以用KADDR(pa)巨集實現。在知道線性地址va的情況下減0xf0000000可以得到實體地址,可以用巨集PADDR(va)實現。

Exercise 4

該實驗要求我們實現:

  1. pgdir_walk()
  2. boot_map_region()
  3. page_lookup()
  4. page_remove()
  5. page_insert()

pagedir_walk():

引數:

  1. pgdir:頁目錄虛擬地址
  2. va:虛擬地址
  3. 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()

引數:

  1. pgdir:頁目錄指標
  2. va:虛擬地址
  3. size:大小
  4. pa:實體地址
  5. 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()

引數:

  1. pgdir:頁目錄指標
  2. pp:PageInfo結構指標,代表一個物理頁
  3. va:線性地址
  4. 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()

引數:

  1. pgdir:頁目錄地址
  2. va:虛擬地址
  3. 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()

引數:

  1. pgdir:頁目錄地址
  2. 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指向的核心頁目錄代表的虛擬地址空間到實體地址空間對映可以用下圖來表示: 核心虛擬地址空間到實體地址空間對映 如何仔細看圖和上面的程式碼,會覺得奇怪,UPAGES開始的這一頁是什麼時候對映的?實際上早在mem_init()開始的時候就有這麼一句kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;,頁目錄表的低PDX(UVPT)項指向頁目錄本身,也就是說虛擬地址UVPT開始處的0x400000位元組對映到實體地址PADDR(kern_pgdir)處。

總結

至此,lab2的所有實驗都已完成。如果順利執行./grade-lab2會看到: 實驗打分 該實驗大體上做三件事:

  1. 提供管理實體記憶體的資料結構和函式
  2. 提供修改頁目錄和頁表樹結構結構的函式,從而達到對映的目的
  3. 用前面兩部分的函式建立核心的線性地址空間

如有錯誤,歡迎指正: 15313676365