我觉得Part2最关键还是这张图,只有深刻理解了Logical Address经过分段机制转换成Linear Address再经过分页机制转换成Physical Address这个过程以及他们的逆过程,才能顺利完成该部分。当然Lab2只涉及线性地址与物理地址的相互转换。
感谢__七把刀__
PDE(page directory entry页目录表项)
PTE(page table entry页表表项)
二级分页。每个程序都有个Page Directory,包括1k个PDE,每个PDE的前20位又可以指向一个Page Table,包括1k个PTE,每个PTE的前20位与linear address的offset字段合在一起就可以指向一个page的物理地址了。就好像每个程序都能独占4G的内存(1k(amount of PDE)*1k(amount of PTE)*4kB(PageSize))。
PTE_P 代表页表format中的P(present)
位 当页表的任意级别的P=0时,该条目不能用于地址转换
在lab2里面可以暂时不要管段翻译,可以看成整个系统是一个段的,即其段基址是0,段范围(limit)是0xffffffff.整个系统就被分成了一个段,所以系统可以看成是纯分页形式的。在分页中,地址的转化也比较简单,JOS被设计成0-256M的物理地址映射到以0xf0000000开始的256M虚拟地址,所以从物理地址转化虚拟地址比较方便简单。
谢谢fang92
补全这些函数并回答自己的问题
pgdir_walk
给定页目录表pgdir(首地址),线性地址va,pgdir_walk()
会返回va在二级页表中对应页表表项(PTE)的地址,是个kernel virtual address。
这个二级页表可能不存在,也就是说pgdir中的页目录表项PDE的P bit为0,那么当create=1时可以新建一个二级页表,并将二级页表的物理地址
赋给对应位置的页目录表项PDE。
无论二级页表存在或是新创建,最后都是返回va在二级页表中对应表项的地址,转换成虚拟地址
/*Given 'pgdir', a pointer to a page directory, pgdir_walk returns
a pointer(virtual address) to the page table entry (PTE) for linear address 'va'.*/
pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
//There are PDX() PET_ADDR() and PTX() in mmu.h
//but I don't know and don't follow the hint...
pde_t pde_num = PDX(va);
pte_t pte_num = PTX(va);
pde_t *pde = pgdir + pde_num;
pte_t *pte;
struct PageInfo *new_PT;
if(((*pde) & PTE_P)==0){
if(create == 0)
return NULL;
new_PT=page_alloc(1);// 0 or 1???
if(new_PT == NULL)
return NULL;
new_PT->pp_ref++;
//why other bit don't need to set?
*pde = page2pa(new_PT) | PTE_P | PTE_U |PTE_W | PTE_AVAIL;
}
pte = (pte_t *)KADDR(PTE_ADDR(*pde));// The first address of the page table
return &pte[pte_num]; //kernal virtual address!!! why?
}
为什么pgdir_walk()最后返回的是一个内核虚拟地址?
为什么page_alloc中memset那也是需要kernel virtual address?
From code executing on the CPU, once we’re in protected mode (which we entered first thing in boot/boot.S), there’s no way to directly use a linear or physical address. All memory references are interpreted as virtual addresses and translated by the MMU, which means
all pointers in C are virtual addresses
.
由此可以看出,凡是C中的指针都是虚拟地址的,所以我认为页目录表表项地址也好、二级页表表项地址也好,在表项内存的都是物理基地址
(的前20位?后12位做标志位),拿出来用就都得转成虚拟地址
boot_map_region
把虚拟地址 [va, va+size)映射到物理地址[pa, pa+size),也就是说让va对应的页表项内存物理地址pa。其中va,pa,size都是4K对齐的
主要映射了三个区域:
第一个是[UPAGES, UPAGES+PTSIZE)
映射到页表存储的物理地址[pages, pages+4M)
。
第二个是[KSTACKTOP-KSTKSIZE, KSTACKTOP)
映射到[bootstack,bootstack+32KB)
处。
第三个则是映射整个内核的虚拟空间[KERNBASE, 2*32-KERNBASE)
到 物理地址[0,256M)
。
谢谢__七把刀__
//Map [va, va+size) of virtual address space to physical [pa, pa+size) in the page table rooted at pgdir
static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
for(size_t i=0; i*PGSIZE < size; i++){
pte_t *pte = pgdir_walk(pgdir, (void *)va, 1);
assert(pte);
*pte = pa| perm | PTE_P; //we don't care the offset???
va+=PGSIZE;
pa+=PGSIZE;
}
}
为什么在boot_map_region中把虚拟地址va映射到物理地址pa不需要管offset?
正常来讲va包含三个字段(dir | table | offset),va映射的物理地址应该是PTE_ADDR(*(*pgdir[dir])[table]) & offset
。我猜想现在讨论的虚拟地址与物理地址针对的是一个物理页page的首地址,12位的offset恰好是一个物理页内的偏移量,所以讨论首地址的时候页内偏移量就可以忽略。
page_lookup
查找虚拟地址va对应的页表项,返回页表项内保存的物理页在PageInfo结构体中的索引值
如果还没有物理页被映射到va,那就返回NULL(包括页目录表项的PTE_P=0或者页表表项的PTE_P=0两种情况)
//Return the page mapped at virtual address 'va'.
struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in
pde_t pde_num = PDX(va);
pde_t *pde = pde_num + pgdir;
if(((*pde) & PTE_P)==0)
return NULL;
pte_t *pte = pgdir_walk(pgdir, va, 0);
if(!pte) // I'm always careless!
return NULL;
if(pte_store != 0)
*pte_store=pte; //a little confusion
return pa2page(PTE_ADDR(*pte));//???do nothing on the offset again?
}
page_remove
取消物理页对虚拟地址va的映射,也就是说删除va在页表中的对应表项(删除表项即取消映射)
如果本来va就不对应一个物理页,那就不用操作
否则,找到va在页表中的表项,表项内容设0,令TLB失效,并把原物理页引用次数pp_ref–,如果pp_ref减为0了就把该物理页free掉,等待再被分配
//Unmaps the physical page at virtual address 'va'.
void page_remove(pde_t *pgdir, void *va)
{
// Fill this function in
pte_t *pte;
//there is a hint above, but I'm still wrong about the &pte
struct PageInfo *un_page=page_lookup(pgdir, va, &pte);
//because PTE may be nonexistent, so PTE_P is necessary
if(un_page && (*pte & PTE_P)){
//if(un_page->pp_ref==1) whether the pp_ref is 1 or not, it has removed a PTE here
tlb_invalidate(pgdir, va);
*pte=0;
page_decref(un_page);
}
}
page_insert
建立PageInfo结构体pp对应物理页与虚拟地址va的映射。实际上就是设置好va在页表中的对应表项内容
如果va已经有映射了(*pte & PTE_P == 1),就remove掉之前的映射
//Map the physical page 'pp' at virtual address 'va'.
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;
pp->pp_ref++;
if(*pte & PTE_P)
page_remove(pgdir, va);
*pte = PTE_ADDR(page2pa(pp)) | perm | PTE_P;
//pp->pp_ref++; why this code has to be up there?
return 0;
}
为什么pp->pp_ref++必须写在page_remove()的前面?
有可能当前要映射的物理页与之前映射的物理页是同一个物理页,那么page_remove()就有可能将这个物理页给free掉,那么这时候再对物理页操作就为时已晚了。
其他问题
page_allloc(1)的参数什么时候设0,什么时候设1?
page_alloc(flag):从page_free_list中取出一页,当flag=1时,初始化该页内存
为什么page_alloc中pp_ref不需要++,在pdgir_walk与page_insert中却都要++?
应该是每一次page被映射到一个虚拟地址va的时候pp_ref需要increment,而如果取消映射就得decrement,page_alloc只是分配了物理页,并没有与虚拟地址建立映射,故不需要改pp_ref值。