/* See COPYRIGHT for copyright information. */ #include #include #include #include #include #include #include #include #include // These variables are set by i386_detect_memory() size_t npages; // Amount of physical memory (in pages) static size_t npages_basemem; // Amount of base memory (in pages) // These variables are set in mem_init() pde_t *kern_pgdir; // Kernel's initial page directory struct PageInfo *pages; // Physical page state array static struct PageInfo *page_free_list; // Free list of physical pages // -------------------------------------------------------------- // Detect machine's physical memory setup. // -------------------------------------------------------------- static int nvram_read(int r) { return mc146818_read(r) | (mc146818_read(r + 1) << 8); } static void i386_detect_memory(void) { size_t basemem, extmem, ext16mem, totalmem; // Use CMOS calls to measure available base & extended memory. // (CMOS calls return results in kilobytes.) basemem = nvram_read(NVRAM_BASELO); extmem = nvram_read(NVRAM_EXTLO); ext16mem = nvram_read(NVRAM_EXT16LO) * 64; // Calculate the number of physical pages available in both base // and extended memory. if (ext16mem) totalmem = 16 * 1024 + ext16mem; else if (extmem) totalmem = 1 * 1024 + extmem; else totalmem = basemem; npages = totalmem / (PGSIZE / 1024); npages_basemem = basemem / (PGSIZE / 1024); cprintf("Physical memory: %uK available, base = %uK, extended = %uK\n", totalmem, basemem, totalmem - basemem); } // -------------------------------------------------------------- // Set up memory mappings above UTOP. // -------------------------------------------------------------- static void mem_init_mp(void); static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm); static void check_page_free_list(bool only_low_memory); static void check_page_alloc(void); static void check_kern_pgdir(void); static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va); static void check_page(void); static void check_page_installed_pgdir(void); // This simple physical memory allocator is used only while JOS is setting // up its virtual memory system. page_alloc() is the real allocator. // // If n>0, allocates enough pages of contiguous physical memory to hold 'n' // bytes. Doesn't initialize the memory. Returns a kernel virtual address. // // If n==0, returns the address of the next free page without allocating // anything. // // If we're out of memory, boot_alloc should panic. // This function may ONLY be used during initialization, // before the page_free_list list has been set up. // Note that when this function is called, we are still using entry_pgdir, // which only maps the first 4MB of physical memory. 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[]; 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(nextfree+n, PGSIZE); return result; } // Set up a two-level page table: // kern_pgdir is its linear (virtual) address of the root // // This function only sets up the kernel part of the address space // (ie. addresses >= UTOP). The user part of the address space // will be set up later. // // From UTOP to ULIM, the user is allowed to read but not write. // Above ULIM the user cannot read or write. void mem_init(void) { uint32_t cr0; size_t n; // Find out how much memory the machine has (npages & npages_basemem). i386_detect_memory(); // Remove this line when you're ready to test this function. //panic("mem_init: This function is not finished\n"); ////////////////////////////////////////////////////////////////////// // create initial page directory. kern_pgdir = (pde_t *) boot_alloc(PGSIZE); memset(kern_pgdir, 0, PGSIZE); ////////////////////////////////////////////////////////////////////// // Recursively insert PD in itself as a page table, to form // a virtual page table at virtual address UVPT. // (For now, you don't have understand the greater purpose of the // following line.) // 通过以下操作: 我们能通过虚拟地址访问到页目录和页表 // Permissions: kernel R, user R kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P; ////////////////////////////////////////////////////////////////////// // 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. // Your code goes here: pages = (struct PageInfo*)boot_alloc(sizeof(struct PageInfo)*npages); memset(pages, 0, sizeof(struct PageInfo)*npages); ////////////////////////////////////////////////////////////////////// // Make 'envs' point to an array of size 'NENV' of 'struct Env'. // LAB 3: Your code here. envs = (struct Env*)boot_alloc(sizeof(struct Env)*NENV); memset(envs, 0, sizeof(struct Env)*NENV); ////////////////////////////////////////////////////////////////////// // Now that we've allocated the initial kernel data structures, we set // up the list of free physical pages. Once we've done so, all further // memory management will go through the page_* functions. In // particular, we can now map memory using boot_map_region // or page_insert page_init(); check_page_free_list(1); check_page_alloc(); check_page(); ////////////////////////////////////////////////////////////////////// // Now we set up virtual memory ////////////////////////////////////////////////////////////////////// // 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: // 要求把pages结构体所在的页面和虚拟地址UPAGES相互映射。 // 这里只要计算出pages结构体的大小,就可以进行映射了。 // 说实话,之前注释有点没看懂。以为要实现虚存对pages指向的物理页的映射 boot_map_region(kern_pgdir, UPAGES, ROUNDUP((sizeof(struct PageInfo)*npages), PGSIZE), PADDR(pages), PTE_U ); ////////////////////////////////////////////////////////////////////// // Map the 'envs' array read-only by the user at linear address UENVS // (ie. perm = PTE_U | PTE_P). // Permissions: // - the new image at UENVS -- kernel R, user R // - envs itself -- kernel RW, user NONE // LAB 3: Your code here. boot_map_region(kern_pgdir, UENVS, ROUNDUP((sizeof(struct Env)*NENV), PGSIZE), PADDR(envs), 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: // extern char bootstacktop[], bootstack[]; 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: uint32_t kern_size = ROUNDUP((0xFFFFFFFF-KERNBASE), PGSIZE); // cprintf("size: %d pages:%d\n", kern_size, kern_size/PGSIZE); boot_map_region(kern_pgdir, (uintptr_t) KERNBASE, kern_size, (physaddr_t)0, PTE_W ); // Initialize the SMP-related parts of the memory map // 这部分与上面的stack映射有点重复吧。 mem_init_mp(); // Check that the initial page directory has been set up correctly. check_kern_pgdir(); // Switch from the minimal entry page directory to the full kern_pgdir // page table we just created. Our instruction pointer should be // somewhere between KERNBASE and KERNBASE+4MB right now, which is // mapped the same way by both page tables. // // If the machine reboots at this point, you've probably set up your // kern_pgdir wrong. lcr3(PADDR(kern_pgdir)); check_page_free_list(0); // entry.S set the really important flags in cr0 (including enabling // paging). Here we configure the rest of the flags that we care about. cr0 = rcr0(); cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP; cr0 &= ~(CR0_TS|CR0_EM); lcr0(cr0); // Some more checks, only possible after kern_pgdir is installed. check_page_installed_pgdir(); } // Modify mappings in kern_pgdir to support SMP // - Map the per-CPU stacks in the region [KSTACKTOP-PTSIZE, KSTACKTOP) // static void mem_init_mp(void) { // Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs. // // For CPU i, use the physical memory that 'percpu_kstacks[i]' refers // to as its kernel stack. CPU i's kernel stack grows down from virtual // address kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP), and is // divided into two pieces, just like the single stack you set up in // mem_init: // * [kstacktop_i - KSTKSIZE, kstacktop_i) // -- backed by physical memory // * [kstacktop_i - (KSTKSIZE + KSTKGAP), kstacktop_i - KSTKSIZE) // -- not backed; so if the kernel overflows its stack, // it will fault rather than overwrite another CPU's stack. // Known as a "guard page". // Permissions: kernel RW, user NONE // // LAB 4: Your code here: size_t i; size_t kstacktop_i; for(i = 0; i < NCPU; i++) { kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP); boot_map_region(kern_pgdir, kstacktop_i - KSTKSIZE, KSTKSIZE, PADDR(&percpu_kstacks[i]), PTE_W ); } } // -------------------------------------------------------------- // 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) { // LAB 4: // Change your code to mark the physical page at MPENTRY_PADDR // as in use // 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! // 1.mark page 0 as in use // 这样我们就可以保留实模式IDT和BIOS结构,以备不时之需。 pages[0].pp_ref = 1; // 2. size_t i; for (i = 1; i < MPENTRY_PADDR/PGSIZE; i++) { pages[i].pp_ref = 0; pages[i].pp_link = page_free_list; page_free_list = &pages[i]; } // cprintf("npage_basemem: %d\n", npages_basemem); npage_basemem:160 // boot APs entry code extern unsigned char mpentry_start[], mpentry_end[]; size_t size = mpentry_end - mpentry_start; size = ROUNDUP(size, PGSIZE); for(;i<(MPENTRY_PADDR+size)/PGSIZE; i++) { pages[i].pp_ref = 1; } for (; i < npages_basemem; i++) { pages[i].pp_ref = 0; pages[i].pp_link = page_free_list; page_free_list = &pages[i]; } // 3.[IOPHYSMEM, EXTPHYSMEM) // mark I/O hole for (;ipp_link; pp->pp_link = NULL; //page2kva 返回值 KernelBase + 物理页号<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_link || pp->pp_ref) { panic("pp->pp_ref is nonzero or pp->pp_link is not NULL\n"); } pp->pp_link = page_free_list; page_free_list = pp; } // // Decrement the reference count on a page, // freeing it if there are no more refs. // void page_decref(struct PageInfo* pp) { if (--pp->pp_ref == 0) page_free(pp); } // 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 uint32_t pdx = PDX(va); // 页目录项索引 uint32_t ptx = PTX(va); // 页表项索引 pte_t *pde; // 页目录项指针 pte_t *pte; // 页表项指针 struct PageInfo *pp; pde = &pgdir[pdx]; //获取页目录项 if (*pde & PTE_P) { // 二级页表有效 // PTE_ADDR得到物理地址,KADDR转为虚拟地址 pte = (KADDR(PTE_ADDR(*pde))); } else { // 二级页表不存在, if (!create) { return NULL; } // 获取一页的内存,创建一个新的页表,来存放页表项 if(!(pp = page_alloc(ALLOC_ZERO))) { return NULL; } pte = (pte_t *)page2kva(pp); pp->pp_ref++; *pde = PADDR(pte) | (PTE_P | PTE_W | PTE_U); // 设置页目录项 } // 返回页表项的虚拟地址 return &pte[ptx]; } // // 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) { size_t pgs = size / PGSIZE; if (size % PGSIZE != 0) { pgs++; } //计算总共有多少页 for (int i = 0; i < pgs; i++) { // 其实可以使用page_insert实现 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; } } // // 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'. // create // - 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); if (!pte) { return -E_NO_MEM; } if (*pte & PTE_P) { if (PTE_ADDR(*pte) == page2pa(pp)) { // 插入的是同一个页面,只需要修改权限等即可 pp->pp_ref--; } else { page_remove(pgdir, va); } } pp->pp_ref++; *pte = page2pa(pp)| perm | PTE_P; return 0; } // // 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 pte_t *pte = pgdir_walk(pgdir, va, 0); if (!pte) { return NULL; } if (pte_store) { *pte_store = pte; // 通过指针的指针返回指针给调用者 } // 难道不用考虑页表项是否存在 if (*pte & PTE_P) { return (pa2page(PTE_ADDR(*pte))); } return NULL; //return pa2page(PTE_ADDR(*pte)); } // // 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; pte_t **pte_store = &pte; struct PageInfo *pi = page_lookup(pgdir, va, pte_store); if (!pi) { return ; } page_decref(pi); // 减引用 **pte_store = 0; // 取消映射 tlb_invalidate(pgdir, va); } // // Invalidate a TLB entry, but only if the page tables being // edited are the ones currently in use by the processor. // void tlb_invalidate(pde_t *pgdir, void *va) { // Flush the entry only if we're modifying the current address space. if (!curenv || curenv->env_pgdir == pgdir) invlpg(va); } // // Reserve size bytes in the MMIO region and map [pa,pa+size) at this // location. Return the base of the reserved region. size does *not* // have to be multiple of PGSIZE. // void * mmio_map_region(physaddr_t pa, size_t size) { // Where to start the next region. Initially, this is the // beginning of the MMIO region. Because this is static, its // value will be preserved between calls to mmio_map_region // (just like nextfree in boot_alloc). static uintptr_t base = MMIOBASE; // Reserve size bytes of virtual memory starting at base and // map physical pages [pa,pa+size) to virtual addresses // [base,base+size). Since this is device memory and not // regular DRAM, you'll have to tell the CPU that it isn't // safe to cache access to this memory. Luckily, the page // tables provide bits for this purpose; simply create the // mapping with PTE_PCD|PTE_PWT (cache-disable and // write-through) in addition to PTE_W. (If you're interested // in more details on this, see section 10.5 of IA32 volume // 3A.) // // Be sure to round size up to a multiple of PGSIZE and to // handle if this reservation would overflow MMIOLIM (it's // okay to simply panic if this happens). // // Hint: The staff solution uses boot_map_region. // // Your code here: // ret -> MMIOBASE 暂存 void *ret = (void *)base; size = ROUNDUP(size, PGSIZE); if (base + size > MMIOLIM || base + size < base) { panic("mmio_map_region reservation overflow\n"); } boot_map_region(kern_pgdir, base, size, pa, PTE_W|PTE_PCD|PTE_PWT); // 为什么需要加以下操作? base 为static! base += size; return ret; } static uintptr_t user_mem_check_addr; // // Check that an environment is allowed to access the range of memory // [va, va+len) with permissions 'perm | PTE_P'. // Normally 'perm' will contain PTE_U at least, but this is not required. // 'va' and 'len' need not be page-aligned; you must test every page that // contains any of that range. You will test either 'len/PGSIZE', // 'len/PGSIZE + 1', or 'len/PGSIZE + 2' pages. // // A user program can access a virtual address if (1) the address is below // ULIM, and (2) the page table gives it permission. These are exactly // the tests you should implement here. // // If there is an error, set the 'user_mem_check_addr' variable to the first // erroneous virtual address. // // Returns 0 if the user program can access this range of addresses, // and -E_FAULT otherwise. // /* int user_mem_check(struct Env *env, const void *va, size_t len, int perm) { // LAB 3: Your code here. size_t vae = ROUNDUP((size_t)(va+len), PGSIZE); size_t vas = ROUNDDOWN((size_t)va, PGSIZE); pte_t * pte; for (; vas < vae; vas += PGSIZE ) { pte = pgdir_walk(env->env_pgdir, (void *)vas, 0); if(!( vas < ULIM && pte==NULL && (*pte & perm))) { if (vas < (size_t)va) { user_mem_check_addr = (uintptr_t)va; } else { user_mem_check_addr = (uintptr_t)vas; } return -E_FAULT; } } return 0; } */ int user_mem_check(struct Env *env, const void *va, size_t len, int perm) { // LAB 3: Your code here. uint32_t start = (uint32_t)ROUNDDOWN((char *)va, PGSIZE); uint32_t end = (uint32_t)ROUNDUP((char *)va+len, PGSIZE); for(; start < end; start += PGSIZE) { pte_t *pte = pgdir_walk(env->env_pgdir, (void*)start, 0); if((start >= ULIM) || (pte == NULL) || !(*pte & PTE_P) || ((*pte & perm) != perm)) { user_mem_check_addr = (start < (uint32_t)va ? (uint32_t)va : start); return -E_FAULT; } } return 0; } // // Checks that environment 'env' is allowed to access the range // of memory [va, va+len) with permissions 'perm | PTE_U | PTE_P'. // If it can, then the function simply returns. // If it cannot, 'env' is destroyed and, if env is the current // environment, this function will not return. // void user_mem_assert(struct Env *env, const void *va, size_t len, int perm) { if (user_mem_check(env, va, len, perm | PTE_U) < 0) { cprintf("[%08x] user_mem_check assertion failure for " "va %08x\n", env->env_id, user_mem_check_addr); env_destroy(env); // may not return } } // -------------------------------------------------------------- // Checking functions. // -------------------------------------------------------------- // // Check that the pages on the page_free_list are reasonable. // static void check_page_free_list(bool only_low_memory) { struct PageInfo *pp; unsigned pdx_limit = only_low_memory ? 1 : NPDENTRIES; int nfree_basemem = 0, nfree_extmem = 0; char *first_free_page; if (!page_free_list) panic("'page_free_list' is a null pointer!"); 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; } *tp[1] = 0; *tp[0] = pp2; page_free_list = pp1; } // if there's a page that shouldn't be on the free list, // try to make sure it eventually causes trouble. for (pp = page_free_list; pp; pp = pp->pp_link) if (PDX(page2pa(pp)) < pdx_limit) memset(page2kva(pp), 0x97, 128); first_free_page = (char *) boot_alloc(0); for (pp = page_free_list; pp; pp = pp->pp_link) { // check that we didn't corrupt the free list itself assert(pp >= pages); assert(pp < pages + npages); assert(((char *) pp - (char *) pages) % sizeof(*pp) == 0); // check a few pages that shouldn't be on the free list assert(page2pa(pp) != 0); assert(page2pa(pp) != IOPHYSMEM); assert(page2pa(pp) != EXTPHYSMEM - PGSIZE); assert(page2pa(pp) != EXTPHYSMEM); assert(page2pa(pp) < EXTPHYSMEM || (char *) page2kva(pp) >= first_free_page); // (new test for lab 4) assert(page2pa(pp) != MPENTRY_PADDR); if (page2pa(pp) < EXTPHYSMEM) ++nfree_basemem; else ++nfree_extmem; } assert(nfree_basemem > 0); assert(nfree_extmem > 0); cprintf("check_page_free_list() succeeded!\n"); } // // Check the physical page allocator (page_alloc(), page_free(), // and page_init()). // static void check_page_alloc(void) { struct PageInfo *pp, *pp0, *pp1, *pp2; int nfree; struct PageInfo *fl; char *c; int i; if (!pages) panic("'pages' is a null pointer!"); // check number of free pages for (pp = page_free_list, nfree = 0; pp; pp = pp->pp_link) ++nfree; // should be able to allocate three pages pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(page2pa(pp0) < npages*PGSIZE); assert(page2pa(pp1) < npages*PGSIZE); assert(page2pa(pp2) < npages*PGSIZE); // temporarily steal the rest of the free pages fl = page_free_list; page_free_list = 0; // should be no free memory assert(!page_alloc(0)); // free and re-allocate? page_free(pp0); page_free(pp1); page_free(pp2); pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(!page_alloc(0)); // test flags memset(page2kva(pp0), 1, PGSIZE); page_free(pp0); assert((pp = page_alloc(ALLOC_ZERO))); assert(pp && pp0 == pp); c = page2kva(pp); for (i = 0; i < PGSIZE; i++) assert(c[i] == 0); // give free list back page_free_list = fl; // free the pages we took page_free(pp0); page_free(pp1); page_free(pp2); // number of free pages should be the same for (pp = page_free_list; pp; pp = pp->pp_link) --nfree; assert(nfree == 0); cprintf("check_page_alloc() succeeded!\n"); } // // Checks that the kernel part of virtual address space // has been set up roughly correctly (by mem_init()). // // This function doesn't test every corner case, // but it is a pretty good sanity check. // static void check_kern_pgdir(void) { uint32_t i, n; pde_t *pgdir; pgdir = kern_pgdir; // check pages array n = ROUNDUP(npages*sizeof(struct PageInfo), PGSIZE); for (i = 0; i < n; i += PGSIZE) assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i); // check envs array (new test for lab 3) n = ROUNDUP(NENV*sizeof(struct Env), PGSIZE); for (i = 0; i < n; i += PGSIZE) assert(check_va2pa(pgdir, UENVS + i) == PADDR(envs) + i); // check phys mem for (i = 0; i < npages * PGSIZE; i += PGSIZE) assert(check_va2pa(pgdir, KERNBASE + i) == i); // check kernel stack // (updated in lab 4 to check per-CPU kernel stacks) for (n = 0; n < NCPU; n++) { uint32_t base = KSTACKTOP - (KSTKSIZE + KSTKGAP) * (n + 1); for (i = 0; i < KSTKSIZE; i += PGSIZE) assert(check_va2pa(pgdir, base + KSTKGAP + i) == PADDR(percpu_kstacks[n]) + i); for (i = 0; i < KSTKGAP; i += PGSIZE) assert(check_va2pa(pgdir, base + i) == ~0); } // check PDE permissions for (i = 0; i < NPDENTRIES; i++) { switch (i) { case PDX(UVPT): case PDX(KSTACKTOP-1): case PDX(UPAGES): case PDX(UENVS): case PDX(MMIOBASE): assert(pgdir[i] & PTE_P); break; default: if (i >= PDX(KERNBASE)) { assert(pgdir[i] & PTE_P); assert(pgdir[i] & PTE_W); } else assert(pgdir[i] == 0); break; } } cprintf("check_kern_pgdir() succeeded!\n"); } // This function returns the physical address of the page containing 'va', // defined by the page directory 'pgdir'. The hardware normally performs // this functionality for us! We define our own version to help check // the check_kern_pgdir() function; it shouldn't be used elsewhere. static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va) { pte_t *p; pgdir = &pgdir[PDX(va)]; if (!(*pgdir & PTE_P)) return ~0; p = (pte_t*) KADDR(PTE_ADDR(*pgdir)); if (!(p[PTX(va)] & PTE_P)) return ~0; return PTE_ADDR(p[PTX(va)]); } // check page_insert, page_remove, &c static void check_page(void) { struct PageInfo *pp, *pp0, *pp1, *pp2; struct PageInfo *fl; pte_t *ptep, *ptep1; void *va; uintptr_t mm1, mm2; int i; extern pde_t entry_pgdir[]; // should be able to allocate three pages pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); // temporarily steal the rest of the free pages fl = page_free_list; page_free_list = 0; // should be no free memory assert(!page_alloc(0)); // there is no page allocated at address 0 assert(page_lookup(kern_pgdir, (void *) 0x0, &ptep) == NULL); // there is no free memory, so we can't allocate a page table assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0); // free pp0 and try again: pp0 should be used for page table page_free(pp0); assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) == 0); assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); assert(check_va2pa(kern_pgdir, 0x0) == page2pa(pp1)); assert(pp1->pp_ref == 1); assert(pp0->pp_ref == 1); // should be able to map pp2 at PGSIZE because pp0 is already allocated for page table assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); // should be no free memory assert(!page_alloc(0)); // should be able to map pp2 at PGSIZE because it's already there assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); // pp2 should NOT be on the free list // could happen in ref counts are handled sloppily in page_insert assert(!page_alloc(0)); // check that pgdir_walk returns a pointer to the pte ptep = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(PGSIZE)])); assert(pgdir_walk(kern_pgdir, (void*)PGSIZE, 0) == ptep+PTX(PGSIZE)); // should be able to change permissions too. assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W|PTE_U) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); assert(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U); assert(kern_pgdir[0] & PTE_U); // should be able to remap with fewer permissions assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_W); assert(!(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U)); // should not be able to map at PTSIZE because need free page for page table assert(page_insert(kern_pgdir, pp0, (void*) PTSIZE, PTE_W) < 0); // insert pp1 at PGSIZE (replacing pp2) assert(page_insert(kern_pgdir, pp1, (void*) PGSIZE, PTE_W) == 0); assert(!(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U)); // should have pp1 at both 0 and PGSIZE, pp2 nowhere, ... assert(check_va2pa(kern_pgdir, 0) == page2pa(pp1)); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1)); // ... and ref counts should reflect this assert(pp1->pp_ref == 2); assert(pp2->pp_ref == 0); // pp2 should be returned by page_alloc assert((pp = page_alloc(0)) && pp == pp2); // unmapping pp1 at 0 should keep pp1 at PGSIZE page_remove(kern_pgdir, 0x0); assert(check_va2pa(kern_pgdir, 0x0) == ~0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1)); assert(pp1->pp_ref == 1); assert(pp2->pp_ref == 0); // test re-inserting pp1 at PGSIZE assert(page_insert(kern_pgdir, pp1, (void*) PGSIZE, 0) == 0); assert(pp1->pp_ref); assert(pp1->pp_link == NULL); // unmapping pp1 at PGSIZE should free it page_remove(kern_pgdir, (void*) PGSIZE); assert(check_va2pa(kern_pgdir, 0x0) == ~0); assert(check_va2pa(kern_pgdir, PGSIZE) == ~0); assert(pp1->pp_ref == 0); assert(pp2->pp_ref == 0); // so it should be returned by page_alloc assert((pp = page_alloc(0)) && pp == pp1); // should be no free memory assert(!page_alloc(0)); // forcibly take pp0 back assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); kern_pgdir[0] = 0; assert(pp0->pp_ref == 1); pp0->pp_ref = 0; // check pointer arithmetic in pgdir_walk page_free(pp0); va = (void*)(PGSIZE * NPDENTRIES + PGSIZE); ptep = pgdir_walk(kern_pgdir, va, 1); ptep1 = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(va)])); assert(ptep == ptep1 + PTX(va)); kern_pgdir[PDX(va)] = 0; pp0->pp_ref = 0; // check that new page tables get cleared memset(page2kva(pp0), 0xFF, PGSIZE); page_free(pp0); pgdir_walk(kern_pgdir, 0x0, 1); ptep = (pte_t *) page2kva(pp0); for(i=0; ipp_ref = 0; // give free list back page_free_list = fl; // free the pages we took page_free(pp0); page_free(pp1); page_free(pp2); // test mmio_map_region mm1 = (uintptr_t) mmio_map_region(0, 4097); mm2 = (uintptr_t) mmio_map_region(0, 4096); // check that they're in the right region assert(mm1 >= MMIOBASE && mm1 + 8192 < MMIOLIM); assert(mm2 >= MMIOBASE && mm2 + 8192 < MMIOLIM); // check that they're page-aligned assert(mm1 % PGSIZE == 0 && mm2 % PGSIZE == 0); // check that they don't overlap assert(mm1 + 8192 <= mm2); // check page mappings assert(check_va2pa(kern_pgdir, mm1) == 0); assert(check_va2pa(kern_pgdir, mm1+PGSIZE) == PGSIZE); assert(check_va2pa(kern_pgdir, mm2) == 0); assert(check_va2pa(kern_pgdir, mm2+PGSIZE) == ~0); // check permissions assert(*pgdir_walk(kern_pgdir, (void*) mm1, 0) & (PTE_W|PTE_PWT|PTE_PCD)); assert(!(*pgdir_walk(kern_pgdir, (void*) mm1, 0) & PTE_U)); // clear the mappings *pgdir_walk(kern_pgdir, (void*) mm1, 0) = 0; *pgdir_walk(kern_pgdir, (void*) mm1 + PGSIZE, 0) = 0; *pgdir_walk(kern_pgdir, (void*) mm2, 0) = 0; cprintf("check_page() succeeded!\n"); } // check page_insert, page_remove, &c, with an installed kern_pgdir static void check_page_installed_pgdir(void) { struct PageInfo *pp, *pp0, *pp1, *pp2; struct PageInfo *fl; pte_t *ptep, *ptep1; uintptr_t va; int i; // check that we can read and write installed pages pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); page_free(pp0); memset(page2kva(pp1), 1, PGSIZE); memset(page2kva(pp2), 2, PGSIZE); page_insert(kern_pgdir, pp1, (void*) PGSIZE, PTE_W); assert(pp1->pp_ref == 1); assert(*(uint32_t *)PGSIZE == 0x01010101U); page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W); assert(*(uint32_t *)PGSIZE == 0x02020202U); assert(pp2->pp_ref == 1); assert(pp1->pp_ref == 0); *(uint32_t *)PGSIZE = 0x03030303U; assert(*(uint32_t *)page2kva(pp2) == 0x03030303U); page_remove(kern_pgdir, (void*) PGSIZE); assert(pp2->pp_ref == 0); // forcibly take pp0 back assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); kern_pgdir[0] = 0; assert(pp0->pp_ref == 1); pp0->pp_ref = 0; // free the pages we took page_free(pp0); cprintf("check_page_installed_pgdir() succeeded!\n"); }