📄 pmap.c~
字号:
/* See COPYRIGHT for copyright information. */#include <inc/x86.h>#include <inc/mmu.h>#include <inc/error.h>#include <inc/string.h>#include <inc/assert.h>#include <kern/pmap.h>#include <kern/kclock.h>#include <kern/env.h>// These variables are set by i386_detect_memory()static physaddr_t maxpa; // Maximum physical addresssize_t npage; // Amount of physical memory (in pages)static size_t basemem; // Amount of base memory (in bytes)static size_t extmem; // Amount of extended memory (in bytes)// These variables are set in i386_vm_init()pde_t* boot_pgdir; // Virtual address of boot time page directoryphysaddr_t boot_cr3; // Physical address of boot time page directorystatic char* boot_freemem; // Pointer to next byte of free memstruct Page* pages; // Virtual address of physical page arraystatic struct Page_list page_free_list; // Free list of physical pages// Global descriptor table.//// The kernel and user segments are identical (except for the DPL).// To load the SS register, the CPL must equal the DPL. Thus,// we must duplicate the segments for the user and the kernel.//struct Segdesc gdt[] ={ // 0x0 - unused (always faults -- for trapping NULL far pointers) SEG_NULL, // 0x8 - kernel code segment [GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0), // 0x10 - kernel data segment [GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0), // 0x18 - user code segment [GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3), // 0x20 - user data segment [GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3), // 0x28 - tss, initialized in idt_init() [GD_TSS >> 3] = SEG_NULL};struct Pseudodesc gdt_pd = { sizeof(gdt) - 1, (unsigned long) gdt};static intnvram_read(int r){ return mc146818_read(r) | (mc146818_read(r + 1) << 8);}voidi386_detect_memory(void){ // CMOS tells us how many kilobytes there are basemem = ROUNDDOWN(nvram_read(NVRAM_BASELO)*1024, PGSIZE); extmem = ROUNDDOWN(nvram_read(NVRAM_EXTLO)*1024, PGSIZE); // Calculate the maximum physical address based on whether // or not there is any extended memory. See comment in <inc/mmu.h>. if (extmem) maxpa = EXTPHYSMEM + extmem; else maxpa = basemem; npage = maxpa / PGSIZE; cprintf("Physical memory: %dK available, ", (int)(maxpa/1024)); cprintf("base = %dK, extended = %dK\n", (int)(basemem/1024), (int)(extmem/1024));}// --------------------------------------------------------------// Set up initial memory mappings and turn on MMU.// --------------------------------------------------------------static void check_boot_pgdir(void);static void check_page_alloc();static void page_check(void);static void boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, physaddr_t pa, int perm);//// A simple physical memory allocator, used only a few times// in the process of setting up the virtual memory system.// page_alloc() is the real allocator.//// Allocate n bytes of physical memory aligned on an // align-byte boundary. Align must be a power of two.// Return kernel virtual address. Returned memory is uninitialized.//// If we're out of memory, boot_alloc should panic.// This function may ONLY be used during initialization,// before the page_free_list has been set up.// static void*boot_alloc(uint32_t n, uint32_t align){ extern char end[]; void *v; // Initialize boot_freemem 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 - // i.e., the first virtual address that the linker // did _not_ assign to any kernel code or global variables. if (boot_freemem == 0) boot_freemem = end; // LAB 2: Your code here: // Step 1: round boot_freemem up to be aligned properly // Step 2: save current value of boot_freemem as allocated chunk char* temp_boot_freemem=ROUNDUP(boot_freemem,align); // Step 3: increase boot_freemem to record allocation boot_freemem=temp_boot_freemem+n; // Step 4: return allocated chunk return (void *)temp_boot_freemem;}// Set up a two-level page table:// boot_pgdir is its linear (virtual) address of the root// boot_cr3 is the physical adresss of the root// Then turn on paging. Then effectively turn off segmentation.// (i.e., the segment base addrs are set to zero).// // This function only sets up the kernel part of the address space// (ie. addresses >= UTOP). The user part of the address space// will be setup later.//// From UTOP to ULIM, the user is allowed to read but not write.// Above ULIM the user cannot read (or write). voidi386_vm_init(void){ pde_t* pgdir; uint32_t cr0; size_t n; // Delete this line: //panic("i386_vm_init: This function is not finished\n"); ////////////////////////////////////////////////////////////////////// // create initial page directory. pgdir = boot_alloc(PGSIZE, PGSIZE); memset(pgdir, 0, PGSIZE); boot_pgdir = pgdir; boot_cr3 = PADDR(pgdir); ////////////////////////////////////////////////////////////////////// // Recursively insert PD in itself as a page table, to form // a virtual page table at virtual address VPT. // (For now, you don't have understand the greater purpose of the // following two lines.) // Permissions: kernel RW, user NONE pgdir[PDX(VPT)] = PADDR(pgdir)|PTE_W|PTE_P; // same for UVPT // Permissions: kernel R, user R pgdir[PDX(UVPT)] = PADDR(pgdir)|PTE_U|PTE_P; ////////////////////////////////////////////////////////////////////// // Make 'pages' point to an array of size 'npage' of 'struct Page'. // The kernel uses this structure to keep track of physical pages; // 'npage' equals the number of physical pages in memory. User-level // programs will get read-only access to the array as well. // You must allocate the array yourself. // Your code goes here: pages = boot_alloc(npage * sizeof(struct Page), PGSIZE); ////////////////////////////////////////////////////////////////////// // Make 'envs' point to an array of size 'NENV' of 'struct Env'. // LAB 3: Your code here. envs=boot_alloc(NENV*sizeof(struct Env),PGSIZE); ////////////////////////////////////////////////////////////////////// // 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_segment or page_insert page_init(); check_page_alloc(); page_check(); ////////////////////////////////////////////////////////////////////// // Now we set up virtual memory ////////////////////////////////////////////////////////////////////// // Map 'pages' read-only by the user at linear address UPAGES // (ie. perm = PTE_U | PTE_P) // Permissions: // - pages -- kernel RW, user NONE // - the read-only version mapped at UPAGES -- kernel R, user R // Your code goes here://<<<<<<< pmap.c boot_map_segment(pgdir, UPAGES, npage * sizeof(struct Page), PADDR(pages), PTE_U | PTE_P);//======= ////////////////////////////////////////////////////////////////////// // Map the 'envs' array read-only by the user at linear address UENVS // (ie. perm = PTE_U | PTE_P). // Permissions: // - envs itself -- kernel RW, user NONE // - the image of envs mapped at UENVS -- kernel R, user R//>>>>>>> 1.1.1.3 boot_map_segment(pgdir,UENVS,NENV*sizeof(struct Env),PADDR(envs),PTE_U|PTE_P); ////////////////////////////////////////////////////////////////////// // Map the kernel stack (symbol name "bootstack"). The complete VA // range of the stack, [KSTACKTOP-PTSIZE, KSTACKTOP), breaks into two // pieces: // * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory // * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed => faults // Permissions: kernel RW, user NONE // Your code goes here: boot_map_segment(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 amapping anyway. // Permissions: kernel RW, user NONE // Your code goes here: boot_map_segment(pgdir, KERNBASE, (0xFFFFFFFF)-KERNBASE + 1, 0, PTE_W|PTE_P); // Check that the initial page directory has been set up correctly. check_boot_pgdir(); ////////////////////////////////////////////////////////////////////// // On x86, segmentation maps a VA to a LA (linear addr) and // paging maps the LA to a PA. I.e. VA => LA => PA. If paging is // turned off the LA is used as the PA. Note: there is no way to // turn off segmentation. The closest thing is to set the base // address to 0, so the VA => LA mapping is the identity. // Current mapping: VA KERNBASE+x => PA x. // (segmentation base=-KERNBASE and paging is off) // From here on down we must maintain this VA KERNBASE + x => PA x // mapping, even though we are turning on paging and reconfiguring // segmentation. // Map VA 0:4MB same as VA KERNBASE, i.e. to PA 0:4MB. // (Limits our kernel to <4MB) pgdir[0] = pgdir[PDX(KERNBASE)]; // Install page table. lcr3(boot_cr3); // Turn on paging. cr0 = rcr0(); cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_TS|CR0_EM|CR0_MP; cr0 &= ~(CR0_TS|CR0_EM); lcr0(cr0); // Current mapping: KERNBASE+x => x => x. // (x < 4MB so uses paging pgdir[0]) // Reload all segment registers. asm volatile("lgdt gdt_pd"); asm volatile("movw %%ax,%%gs" :: "a" (GD_UD|3)); asm volatile("movw %%ax,%%fs" :: "a" (GD_UD|3)); asm volatile("movw %%ax,%%es" :: "a" (GD_KD)); asm volatile("movw %%ax,%%ds" :: "a" (GD_KD)); asm volatile("movw %%ax,%%ss" :: "a" (GD_KD)); asm volatile("ljmp %0,$1f\n 1:\n" :: "i" (GD_KT)); // reload cs asm volatile("lldt %%ax" :: "a" (0)); // Final mapping: KERNBASE+x => KERNBASE+x => x. // This mapping was only used after paging was turned on but // before the segment registers were reloaded. pgdir[0] = 0; // Flush the TLB for good measure, to kill the pgdir[0] mapping. lcr3(boot_cr3);}//// Check the physical page allocator (page_alloc(), page_free(),// and page_init()).//static voidcheck_page_alloc(){ struct Page *pp, *pp0, *pp1, *pp2; struct Page_list fl; // if there's a page that shouldn't be on // the free list, try to make sure it // eventually causes trouble. LIST_FOREACH(pp0, &page_free_list, pp_link) memset(page2kva(pp0), 0x97, 128); // should be able to allocate three pages pp0 = pp1 = pp2 = 0; assert(page_alloc(&pp0) == 0); assert(page_alloc(&pp1) == 0); assert(page_alloc(&pp2) == 0); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(page2pa(pp0) < npage*PGSIZE); assert(page2pa(pp1) < npage*PGSIZE); assert(page2pa(pp2) < npage*PGSIZE); // temporarily steal the rest of the free pages fl = page_free_list; LIST_INIT(&page_free_list); // should be no free memory assert(page_alloc(&pp) == -E_NO_MEM); // free and re-allocate? page_free(pp0); page_free(pp1); page_free(pp2); pp0 = pp1 = pp2 = 0; assert(page_alloc(&pp0) == 0); assert(page_alloc(&pp1) == 0); assert(page_alloc(&pp2) == 0); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(page_alloc(&pp) == -E_NO_MEM); // give free list back page_free_list = fl; // free the pages we took page_free(pp0); page_free(pp1); page_free(pp2); cprintf("check_page_alloc() succeeded!\n");}//// Checks that the kernel part of virtual address space// has been setup roughly correctly(by i386_vm_init()).//// This function doesn't test every corner case,// in fact it doesn't test the permission bits at all,// but it is a pretty good sanity check. //static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va);static voidcheck_boot_pgdir(void){ uint32_t i, n; pde_t *pgdir; pgdir = boot_pgdir; // check pages array n = ROUNDUP(npage*sizeof(struct Page), 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 < npage; i += PGSIZE) assert(check_va2pa(pgdir, KERNBASE + i) == i); // check kernel stack for (i = 0; i < KSTKSIZE; i += PGSIZE) assert(check_va2pa(pgdir, KSTACKTOP - KSTKSIZE + i) == PADDR(bootstack) + i); // check for zero/non-zero in PDEs for (i = 0; i < NPDENTRIES; i++) { switch (i) { case PDX(VPT): case PDX(UVPT): case PDX(KSTACKTOP-1): case PDX(UPAGES): case PDX(UENVS): assert(pgdir[i]); break; default: if (i >= PDX(KERNBASE)) assert(pgdir[i]); else assert(pgdir[i] == 0); break; } } cprintf("check_boot_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_boot_pgdir() function; it shouldn't be used elsewhere.static physaddr_tcheck_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)]);} // --------------------------------------------------------------// Tracking of physical pages.// The 'pages' array has one 'struct Page' 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 point, ONLY use the functions below// to allocate and deallocate physical memory via the page_free_list,// and NEVER use boot_alloc()//voidpage_init(void){ // The example code here marks all pages as free. // However this is not truly the case. What memory is free? // 1) Mark 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) Mark the rest of base memory as free. // 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM). // Mark it as in use so that it can never be allocated. // 4) Then extended memory [EXTPHYSMEM, ...).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -