// map kernel text executable and read-only. kvmmap(KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// map kernel data and the physical RAM we'll make use of. kvmmap((uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// map the trampoline for trap entry/exit to // the highest virtual address in the kernel. kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X); }
// add a mapping to the kernel page table. // only used when booting. // does not flush TLB or enable paging. void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm) { if (mappages(kernel_pagetable, va, sz, pa, perm) != 0) { panic("kvmmap"); } }
// Create PTEs for virtual addresses starting at va that refer to // physical addresses starting at pa. va and size might not // be page-aligned. Returns 0 on success, -1 if walk() couldn't // allocate a needed page-table page. int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) { uint64 a, last; pte_t *pte;
a = PGROUNDDOWN(va); last = PGROUNDDOWN(va + size - 1); for(;;){ if ((pte = walk(pagetable, a, 1)) == 0) return -1; if (*pte & PTE_V) panic("remap"); *pte = PA2PTE(pa) | perm | PTE_V; if (a == last) break; a += PGSIZE; pa += PGSIZE; } return 0; }
// Return the address of the PTE in page table pagetable // that corresponds to virtual address va. If alloc!=0, // create any required page-table pages. // // The risc-v Sv39 scheme has three levels of page-table // pages. A page-table page contains 512 64-bit PTEs. // A 64-bit virtual address is split into five fields: // 39..63 -- must be zero. // 30..38 -- 9 bits of level-2 index. // 21..29 -- 9 bits of level-1 index. // 12..20 -- 9 bits of level-0 index. // 0..11 -- 12 bits of byte offset within the page. pte_t * walk(pagetable_t pagetable, uint64 va, int alloc) { if(va >= MAXVA) panic("walk");
//riscv.h // shift a physical address to the right place for a PTE. #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
#define PTE2PA(pte) (((pte) >> 10) << 12)
#define PTE_FLAGS(pte) ((pte) & 0x3FF)
// extract the three 9-bit page table indices from a virtual address. #define PXMASK 0x1FF // 9 bits #define PXSHIFT(level) (PGSHIFT+(9*(level))) #define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)
kvminithart()
1 2 3 4 5 6 7 8
// Switch h/w page table register to the kernel's page table, // and enable paging. void kvminithart() { w_satp(MAKE_SATP(kernel_pagetable)); sfence_vma(); }
kvminithart将硬件的页表寄存器(SATP)切换到内核的页表,实现分页机制,w_stap是riscv.h中的一个函数,将内核页表写入SATP(Supervisor Address Translation and Protection)寄存器,SATP存储了页表的物理地址,sfence_vma用于刷新处理器缓存中的地址转换信息
// supervisor address translation and protection; // holds the address of the page table. static inline void w_satp(uint64 x) { asm volatile("csrw satp, %0" : : "r" (x)); }
// flush the TLB. static inline void sfence_vma() { // the zero, zero means flush all TLB entries. asm volatile("sfence.vma zero, zero"); }
kvmpa()
将内核虚拟地址转换成物理地址,先计算虚拟地址在页内的偏移量,再求页表项的首地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// translate a kernel virtual address to // a physical address. only needed for // addresses on the stack. // assumes va is page aligned. uint64 kvmpa(uint64 va) { uint64 off = va % PGSIZE; pte_t *pte; uint64 pa; pte = walk(kernel_pagetable, va, 0); if(pte == 0) panic("kvmpa"); if((*pte & PTE_V) == 0) panic("kvmpa"); pa = PTE2PA(*pte); return pa+off; }
uvmcreate() uvminit()
uvmcreate() 用于创建一个新的用户页表,给其分配物理内存
1 2 3 4 5 6 7 8 9 10 11 12
// create an empty user page table. // returns 0 if out of memory. pagetable_t uvmcreate() { pagetable_t pagetable; pagetable = (pagetable_t) kalloc(); if(pagetable == 0) return 0; memset(pagetable, 0, PGSIZE); return pagetable; }
// Load the user initcode into address 0 of pagetable, // for the very first process. // sz must be less than a page. void uvminit(pagetable_t pagetable, uchar *src, uint sz) { char *mem;
if(sz >= PGSIZE) panic("inituvm: more than a page"); mem = kalloc(); memset(mem, 0, PGSIZE); mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U); memmove(mem, src, sz); }
// Allocate PTEs and physical memory to grow process from oldsz to // newsz, which need not be page aligned. Returns new size or 0 on error. uint64 uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) { char *mem; uint64 a;
if(newsz < oldsz) return oldsz;
oldsz = PGROUNDUP(oldsz); for(a = oldsz; a < newsz; a += PGSIZE){ mem = kalloc(); if(mem == 0){ uvmdealloc(pagetable, a, oldsz); return 0; } memset(mem, 0, PGSIZE); if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){ kfree(mem); uvmdealloc(pagetable, a, oldsz); return 0; } } return newsz; }
uvmdealloc()则是减少一个进程的虚拟内存空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Deallocate user pages to bring the process size from oldsz to // newsz. oldsz and newsz need not be page-aligned, nor does newsz // need to be less than oldsz. oldsz can be larger than the actual // process size. Returns the new process size. uint64 uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) { if(newsz >= oldsz) return oldsz;
// Remove npages of mappings starting from va. va must be // page-aligned. The mappings must exist. // Optionally free the physical memory. void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) { uint64 a; pte_t *pte;
if((va % PGSIZE) != 0) panic("uvmunmap: not aligned");
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ if((pte = walk(pagetable, a, 0)) == 0) panic("uvmunmap: walk"); if((*pte & PTE_V) == 0) panic("uvmunmap: not mapped"); if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ uint64 pa = PTE2PA(*pte); kfree((void*)pa); } *pte = 0; } }
freewalk() 通过递归释放页表中的所有页表页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Recursively free page-table pages. // All leaf mappings must already have been removed. void freewalk(pagetable_t pagetable) { // there are 2^9 = 512 PTEs in a page table. for(int i = 0; i < 512; i++){ pte_t pte = pagetable[i]; if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){ // this PTE points to a lower-level page table. uint64 child = PTE2PA(pte); freewalk((pagetable_t)child); pagetable[i] = 0; } else if(pte & PTE_V){ panic("freewalk: leaf"); } } kfree((void*)pagetable); }
Print a page table (easy)
YOUR JOB 定义一个名为vmprint()的函数。它应当接收一个pagetable_t作为参数,并以下面描述的格式打印该页表。在exec.c中的return argc之前插入if(p->pid==1) vmprint(p->pagetable),以打印第一个进程的页表
1 2 3 4 5 6 7 8 9 10 11
page table 0x0000000087f6e000 ..0: pte 0x0000000021fda801 pa 0x0000000087f6a000 .. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000 .. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000 .. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000 .. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000 ..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000 .. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000 .. .. ..510: pte 0x0000000021fdd807 pa 0x0000000087f76000 .. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
// map kernel text executable and read-only. uvmmap(k_pgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// map kernel data and the physical RAM we'll make use of. uvmmap(k_pgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// map the trampoline for trap entry/exit to // the highest virtual address in the kernel. uvmmap(k_pgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
return k_pgtbl; }
在allocproc中调用
1 2 3 4 5 6 7 8 9 10
// proc.c // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; }
p->k_pgtbl = k_pgtbl_init_in_process();
为新创建的页表的内核栈建立映射,在allocproc中调用
同样仿照kvmmap()去写,通过mappages()去做映射
1 2 3 4 5 6
// vm.c void uvmmap(pagetable_t pagetable, uint64 va, uint64 pa, uint64 sz, int perm) { if (mappages(pagetable, va, sz, pa, perm) != 0) { panic("uvmmap"); } }
在上面的allocproc中进行调用
1 2 3 4 5 6 7 8
p->k_pgtbl = k_pgtbl_init_in_process();
char *pa = kalloc(); if (pa == 0) panic("kalloc"); uint64 va = KSTACK((int) (p - proc)); uvmmap(p->k_pgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W); p->kstack = va;
// translate a kernel virtual address to // a physical address. only needed for // addresses on the stack. // assumes va is page aligned. uint64 kvmpa(uint64 va) { uint64 off = va % PGSIZE; pte_t *pte; uint64 pa; pte = walk(myproc()->k_pgtbl, va, 0); // 改成myproc()->k_pgtbl if(pte == 0) panic("kvmpa"); if((*pte & PTE_V) == 0) panic("kvmpa"); pa = PTE2PA(*pte); return pa+off; }