Mit os Lab 2. Memory Management
Posted ym65536
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 Your code should now pass the |
注意下面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");
以上是关于Mit os Lab 2. Memory Management的主要内容,如果未能解决你的问题,请参考以下文章
MIT 6.828 JOS学习笔记7. Lab 1 Part 2.2: The Boot Loader
MIT 6.828 JOS学习笔记4. Lab 1 Part 2.1: The Boot Loader