Mit os Lab 2. Memory Management

Part 1: Physical Page Management

Exercise 1. In the file kern/pmap.c, you must implement code for the following functions (probably in the order given).

boot_alloc()
mem_init() (only up to the call to check_page_free_list(1))
page_init()
page_alloc()
page_free()

check_page_free_list() and check_page_alloc() test your physical page allocator. You should boot JOS and see whether check_page_alloc() reports success. Fix your code so that it passes. You may find it helpful to add your own assert()s to verify that your assumptions are correct.

在lab1中,内存布局如下:

kernel是0xF0100000 - end 部分, 剩下4K大小是页目录表:

需要由函数boot_alloc填补。

这部分的地址都是线性地址,即line_addr: 0xF0100000 ==> phy_addr: 0x00100000

PADDR: 线性地址转物理地址, kva-KERNBASE

KADDR: 物理地址转线性地址,pva+KERNBASE

page2pa: 页表项转物理地址,全局变量pages表示页表项的起始地址, pp-pages表示第k个页, 则(pp-pages)<<PAGE_SHIFT则为页表项pp的物理地址。

 

1) boot_alloc

 1 static void *
 2 boot_alloc(uint32_t n)
 3 {
 4     static char *nextfree;    // virtual address of next byte of free memory
 5     char *result;
 6 
 7     // Initialize nextfree if this is the first time.
 8     // 'end' is a magic symbol automatically generated by the linker,
 9     // which points to the end of the kernel's bss segment:
10     // the first virtual address that the linker did *not* assign
11     // to any kernel code or global variables.
12     if (!nextfree) {
13         extern char end[];
14         nextfree = (char *)ROUNDUP((char *) end, PGSIZE);
15     }
16 
17     // Allocate a chunk large enough to hold 'n' bytes, then update
18     // nextfree.  Make sure nextfree is kept aligned
19     // to a multiple of PGSIZE.
20     //
21     // LAB 2: Your code here.
22     cprintf("boot_alloc memory at line_addr [%08x, %08x + %08x]\n", nextfree, nextfree, ROUNDUP(n, PGSIZE));
23     result = nextfree;
24     nextfree += ROUNDUP(n, PGSIZE);
25 
26     return result;
27 }

这里有个技巧,局部静态变量nextfree未初始化时默认为0,第一次会执行12-15行,再次调用则不会。

 

2) 完善mem_init

这里需要分配页表项,内存布局如下:

在mem_init中,已经通过kern_pgdir = (pde_t *) boot_alloc(PGSIZE);分配了页目录表,完善分配页表项PagesInfo:

 1     //////////////////////////////////////////////////////////////////////
 2     // Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
 3     // The kernel uses this array to keep track of physical pages: for
 4     // each physical page, there is a corresponding struct PageInfo in this
 5     // array.  'npages' is the number of physical pages in memory.  Use memset
 6     // to initialize all fields of each struct PageInfo to 0.
 7     // Your code goes here:
 8     pages = (struct PageInfo* )boot_alloc(npages * sizeof(struct PageInfo));
 9     memset(pages, 0, npages * sizeof(struct PageInfo));
10     cprintf("npages:%d, npages_basemem:%d, pages_addr:%08x\n", npages, npages_basemem, pages);

 

3) page_init

通过全局变量page_free_list,将所有的页表项PageInfo和4K大小的页一一映射。

内存分配 [PAGE0][PGSIZE, npages_basemem * PGSIZE)[IOPHYSMEM, EXTPHYSMEM)[EXTPHYSMEM, ...)

PAGE0留作BIOS和IDT等,PAGE1-npages_basemem可以分配,IOmem到EXTmem用于IO, 之后是EXTPHYSMEM,

EXTPHYSMEM的起始部分到nextfree会用作kernel、页目录、页表项等,应该从nextfree再开始分配。

 1 void
 2 page_init(void)
 3 {
 4     // The example code here marks all physical pages as free.
 5     // However this is not truly the case.  What memory is free?
 6     //  1) Mark physical page 0 as in use.
 7     //     This way we preserve the real-mode IDT and BIOS structures
 8     //     in case we ever need them.  (Currently we don't, but...)
 9     //  2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
10     //     is free.
11     //  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
12     //     never be allocated.
13     //  4) Then extended memory [EXTPHYSMEM, ...).
14     //     Some of it is in use, some is free. Where is the kernel
15     //     in physical memory?  Which pages are already in use for
16     //     page tables and other data structures?
17     //
18     // Change the code to reflect this.
19     // NB: DO NOT actually touch the physical memory corresponding to
20     // free pages!
21     size_t i;
22     pages[0].pp_ref = 1;
23     pages[0].pp_link = NULL;
24 
25     uint32_t nextfree = (uint32_t)boot_alloc(0);
26     cprintf("NPAGES: %d NPAGES_BASE_MEM: %d\n", npages, npages_basemem);
27     cprintf("NEXTFREE: %08x IOPHY: %08x  EXT: %08x\n", nextfree - KERNBASE, IOPHYSMEM, EXTPHYSMEM);
28     for (i = 1; i < npages; i++) 
29     {
30         if ((i >= (IOPHYSMEM / PGSIZE)) && (i < ((nextfree - KERNBASE)/ PGSIZE))) 
31         {
32             pages[i].pp_ref = 1;
33             pages[i].pp_link = NULL;
34         }
35         else 
36         {
37             pages[i].pp_ref = 0;
38             pages[i].pp_link = page_free_list;
39             page_free_list = &pages[i];
40         }
41     }
42 }

 

4) page_alloc

page_alloc函数的实现. 就是把当前free list中的空闲页释放一个,然后更新page_free_list,让ta指向下一个空闲页即可

如果传入ALLOC_ZERO的flag,则用memset清零。

 1 struct PageInfo *
 2 page_alloc(int alloc_flags)
 3 {
 4     // Fill this function in
 5     struct PageInfo* pginfo = NULL;
 6     if (!page_free_list)
 7     {
 8         return NULL;
 9     }
10 
11     pginfo = page_free_list;
12     page_free_list = pginfo->pp_link;
13     if (alloc_flags & ALLOC_ZERO)
14     {
15         memset(page2kva(pginfo), 0, PGSIZE);
16     }
17 
18     return pginfo;
19 }

 

5) page_free

对应的page_free就是把pp描述的page加入到free list当中去,使得pp成为最新的page_free_list.

 1 void
 2 page_free(struct PageInfo *pp)
 3 {
 4     // Fill this function in
 5     // Hint: You may want to panic if pp->pp_ref is nonzero or
 6     // pp->pp_link is not NULL.
 7 
 8     assert(pp->pp_ref == 0 || pp->pp_link == NULL);
 9 
10     pp->pp_link = page_free_list;
11     page_free_list = pp;
12 }

 

Part 2: Virtual Memory

在Linux下, 每个进程都有自己独立的地址空间, 32bit的系统下位4GB. 所以, 每个地址的长度都是四字节, 也正好是一个指针的大小. 在了解了Linux的分页机制之后, 可以看到一个Virtual address其实是由如下3个部分组成:

// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |      Index     |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
//  \---------- PGNUM(la) ----------/

页目录(Page directory)其实是一个长度为1024的整形数组, 里面的每个元素是指向每一个页表(Page table)的指针. 每个页表也是个长度为1024的整形数组, 里边的元素则是物理地址的值.

然一个虚拟地址的高10位是该地址对应的页目录索引, 用于获取页目录中指向该地址的页表的地址.

通过10~20位, 能够得到该地址在页表项的索引, 然后就能够得到该地址对应的物理地址, 最后, 虚拟地址的低12位加上物理地址的基地址. 就完成了由虚拟地址到物理地址的转换.

 如图所示:

 

1) pgdir_walk

pgdir_walk 根据全局pgdir和虚拟地址va,获取va所在的页表项pte。

 1 pte_t *
 2 pgdir_walk(pde_t *pgdir, const void *va, int create)
 3 {
 4     // Fill this function in
 5     int pd_idx = PDX(va);
 6     int pte_idx = PTX(va);
 7     if (pgdir[pd_idx] & PTE_P) // if pde exist
 8     {
 9         pte_t *ptebase = KADDR(PTE_ADDR(pgdir[pd_idx]));  // 这里ptebase指向上图中的Page Table基地址
10         return ptebase + pte_idx; 
11     }
12     // pde not exist
13     if (!create)
14         return NULL;
15     struct PageInfo* pg = page_alloc(ALLOC_ZERO);
16     if (!pg)
17         return NULL;
18     pg->pp_ref++;
19     pgdir[pd_idx] = page2pa(pg) | PTE_P | PTE_U | PTE_W;  // 初始化PageDirectory中的pd_idx项
20 
21     pte_t *ptebase = KADDR(PTE_ADDR(pgdir[pd_idx]));
22     return ptebase + pte_idx;
23 }

 

2) boot_map_region

boot_map_region函数将虚拟地址[va,va+size)的区域映射到物理地址pa开始的物理内存中。

 1 static void
 2 boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
 3 {
 4     // Fill this function in
 5     int i;
 6     cprintf("start: VA %08x mapped to PA %08x, size:%08x\n", va, pa, size);
 7     for (i = 0; i < size / PGSIZE; i++)
 8     {
 9         pte_t* pte = pgdir_walk(pgdir, (void* )va, 1); //create
10         if (!pte)
11             panic("boot_map_region panic: out of memory!\n");
12         *pte = pa | perm | PTE_P;   // 初始化pte
13         va += PGSIZE;
14         pa += PGSIZE;
15     }
16     cprintf("end: VA %08x mapped to PA %08x, size:%08x\n", va, pa, size);
17 }

 

3) page_lookup

page_lookup函数检测va虚拟地址的虚拟页是否存在不存在返回NULL,

存在返回描述该虚拟地址关联物理内存页的描述结构体PageInfo的指针

 1 struct PageInfo *
 2 page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
 3 {
 4     // Fill this function in
 5     pte_t * pte = pgdir_walk(pgdir, va, 0);
 6 
 7     if(!pte)
 8     {
 9         return NULL;
10     }
11 
12     *pte_store = pte;
13 
14     return pa2page(PTE_ADDR(*pte));
15 }

 

4) page_remove

void
page_remove(pde_t *pgdir, void *va)
{
    // Fill this function in
    pte_t* pte;
    struct PageInfo* pp = page_lookup(pgdir, va, &pte);
    if (!pp)
        return;

    page_decref(pp);
    *pte = 0;
    tlb_invalidate(pgdir, va);
}

 

5) page_insert

page_insert 把pp描述的物理页与虚拟地址va关联起来

如果va所在的虚拟内存页不存在,那么pgdir_walk的create为1,创建这个虚拟页

如果va所在的虚拟内存页存在,那么取消当前va的虚拟内存页也和之前物理页的关联,并且为va建立新的物理页联系——pp所描述的物理页

 1 int
 2 page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
 3 {
 4     // Fill this function in
 5     
 6     pte_t *pte = pgdir_walk(pgdir, va, 0);
 7     physaddr_t ppa = page2pa(pp);
 8 
 9     if(pte)
10     {
11         if(*pte & PTE_P)
12         {
13             page_remove(pgdir, va);  // 取消va与之前物理页的映射
14         }
15 
16         if(page_free_list == pp)
17         {
18             page_free_list = page_free_list->pp_link;
19         }
20     }
21     else
22     {
23         pte = pgdir_walk(pgdir, va, 1);
24         if(!pte)
25         {
26             return -E_NO_MEM;
27         }
28 
29     }
30 
31     *pte = page2pa(pp) | PTE_P | perm; // 创建pp与va的映射
32 
33     pp->pp_ref++;
34     tlb_invalidate(pgdir, va);
35     return 0;
36 }

 

Part 3: Kernel Address Space

nitializing the Kernel Address Space 已经差不多了, 接下来我们需要初始化内存空间.

Exercise 5. Fill in the missing code in mem_init() after the call to check_page().

Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.

注意下面ULIM是分界线,ULIM以上是内核地址空间,以下是用户空间

这个页面布局代表的是启用地址转换以后,无论是操作系统还是用户程序,看到的虚拟内存布局,这也就是说,操
操作系统和用户程序使用的是同一套页目录和页表。

    //////////////////////////////////////////////////////////////////////
    // 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:

    boot_map_region(kern_pgdir,
                    UPAGES,
                    ROUNDUP((sizeof(struct PageInfo) * npages), PGSIZE),
                    PADDR(pages),
                    (PTE_U | PTE_P));

    //////////////////////////////////////////////////////////////////////
    // 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:
    
    boot_map_region(kern_pgdir,
                (KSTACKTOP - KSTKSIZE),
                KSTKSIZE,
                PADDR(bootstack),
                (PTE_W | PTE_P));

    //////////////////////////////////////////////////////////////////////
    // 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,
                ROUNDUP((0xFFFFFFFF - KERNBASE), PGSIZE),
                0,
                (PTE_W) | (PTE_P));

    // Check that the initial page directory has been set up correctly.
    check_kern_pgdir();
    cprintf("so far, exercise 5 works well :)\n");

 

posted on 2016-06-26 16:29  ym65536  阅读(1031)  评论(0编辑  收藏  举报