• 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]
    ", 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
    ", 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
    ", npages, npages_basemem);
    27     cprintf("NEXTFREE: %08x IOPHY: %08x  EXT: %08x
    ", 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
    ", 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!
    ");
    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
    ", 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 :)
    ");
  • 相关阅读:
    Java 7 中 NIO.2 的使用——第二节 元数据文件的属性
    Java 7 中 NIO.2 的使用——第一节 Path 类的使用
    使用第三方工具覆写Object中方法
    Java Synchronized Blocks vs. Methods
    生如夏花,死如秋叶
    Struts2中的ModelDriven机制及其运用(转)
    Java 调用 Javascript 函数的范例
    枚举实现工厂模式
    使用Java 8 Lambda表达式对Employee类进行操作
    自定义异常时如何定义checked异常和unchecked异常
  • 原文地址:https://www.cnblogs.com/ym65536/p/5618097.html
Copyright © 2020-2023  润新知