之前我们建立了基本的物理页,下面我们建立系统的虚拟地址系统,也就是二级页表。
对于所有对内存的访问,地址都被解释成虚拟地址,并由MMU 执行过程。程序员的工作是设置和维护页目录和页表。
实现虚拟页表的关键在于页目录,页表中存的是物理地址,而页目录项,页表项本身地址是虚拟地址。
pgdir_walk()这个函数的主要功能如下:
·根据给定页目录指针pgdir,返回线性地址va对应的页表项的指针。
·如果相关页表还不存在,且create==false,返回NULL。
·否则,通过page_alloc为页表分配一页物理内存,并相应初始化,清0。返回va对应的表项的指针。
pte_t * pgdir_walk(pde_t *pgdir, const void *va, int create) { // Fill this function in pde_t *pgdir_entry = &pgdir[PDX(va)];//页目录获取页目录项,里面存的是物理地址 pte_t *pgtable;//虚拟地址 struct PageInfo *pp; if(*pgdir_entry & PTE_P) { pgtable = (pte_t*)KADDR(PTE_ADDR(*pgdir_entry));//如果存在的话,物理地址转化为虚拟地址 } else { if(create ==false) return NULL; if((pp = page_alloc(ALLOC_ZERO)) ==0) return NULL; pp->pp_ref = 1; pgtable = (pte_t*)KADDR(page2pa(pp));//将页的虚拟地址转化为物理地址 *pgdir_entry = PADDR(pgtable) | PTE_P |PTE_W |PTE_U; } return &pgtable[PTX(va)]; }
boot_map_region()这个函数的主要功能如下:
·根据指定页目录pgdir,权限perm,页大小PGSIZE,将[va, va+size)的虚拟地址映射到物理地址的[pa, pa+size)。
static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm) { // Fill this function in uintptr_t offset; pte_t *pte; for(offset =0 ;offset < size ;offset+=PGSIZE) { pte = pgdir_walk(pgdir,(char *)va,1); if(!pte) panic("boot_map_region:pgdir_walk failed"); if(*pte & PTE_P) panic("boot_map_region:pte remap"); *pte = pa | perm |PTE_P; va +=PGSIZE; pa +=PGSIZE; } }
下面我来说一下JOS的内存分布图,默认是低地址留给用户空间,高地址留给kernel,从0到ULIM为用户空间,从ULIM到4GB为kernel address。
另外需要对每一块规定权限,地址空间大致被分为三个部分:[0, UTOP)是用户可以读写的;[UTOP,ULIM)对用户和内核都是只读的,这部分地址的作用是向用户暴露一部分内核的数据内容;ULIM以上的地址,用户没有任何权限。
另外kernelstack是从高向低地址生长的,[KSTACKTOP-KSTKSIZE, KSTACKTOP)是真正会使用到的堆栈空间;而[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE)这部分,是为了防止栈溢出时,不小心修改到紧接着的地址空间中的数据,也就是说这是一个”guard page”,即缓冲区。这体现了JOS考虑问题的保守性和稳健性。所以[KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE)中的虚拟地址仅仅是保留的,不需要映射。
我们将KERNBASE开始映射到整个物理地址空间。这样做的目的,是为了稍后打开页式转换以后,Kernel也能使用统一的虚拟地址来访问其自身数据,即Kernel使用虚拟地址KERNBASE+x访问到的物理地址,正是物理地址为x处的内存。
/* * Virtual memory map: Permissions * kernel/user * * 4 Gig --------> +------------------------------+ * | | RW/-- * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * : . : * : . : * : . : * |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/-- * | | RW/-- * | Remapped Physical Memory | RW/-- * | | RW/-- * KERNBASE, ----> +------------------------------+ 0xf0000000 --+ * KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE | * | - - - - - - - - - - - - - - -| | * | Invalid Memory (*) | --/-- KSTKGAP | * +------------------------------+ | * | CPU1's Kernel Stack | RW/-- KSTKSIZE | * | - - - - - - - - - - - - - - -| PTSIZE * | Invalid Memory (*) | --/-- KSTKGAP | * +------------------------------+ | * : . : | * : . : | * MMIOLIM ------> +------------------------------+ 0xefc00000 --+ * | Memory-mapped I/O | RW/-- PTSIZE * ULIM, MMIOBASE --> +------------------------------+ 0xef800000 * | Cur. Page Table (User R-) | R-/R- PTSIZE * UVPT ----> +------------------------------+ 0xef400000 * | RO PAGES | R-/R- PTSIZE * UPAGES ----> +------------------------------+ 0xef000000 * | RO ENVS | R-/R- PTSIZE * UTOP,UENVS ------> +------------------------------+ 0xeec00000 * UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE * +------------------------------+ 0xeebff000 * | Empty Memory (*) | --/-- PGSIZE * USTACKTOP ---> +------------------------------+ 0xeebfe000 * | Normal User Stack | RW/RW PGSIZE * +------------------------------+ 0xeebfd000 * | | * | | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * . . * . . * . . * |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| * | Program Data & Heap | * UTEXT --------> +------------------------------+ 0x00800000 * PFTEMP -------> | Empty Memory (*) | PTSIZE * | | * UTEMP --------> +------------------------------+ 0x00400000 --+ * | Empty Memory (*) | | * | - - - - - - - - - - - - - - -| | * | User STAB Data (optional) | PTSIZE * USTABDATA ----> +------------------------------+ 0x00200000 | * | Empty Memory (*) | | * 0 ------------> +------------------------------+ --+ * * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. * "Empty Memory" is normally unmapped, but user programs may map pages * there if desired. JOS user programs map pages temporarily at UTEMP. */