使用多级页表
我们需要把页表放在内存里,并且需要有办法修改页表,比如在页表里增加一个页面的映射或者删除某个页面的映射。
最主要的是两个接口:
page_insert()
,在页表里建立一个映射
page_remove()
,在页表里删除一个映射
这些我们都在kern/mm/pmm.c
里面编写。
我们来看page_insert(),page_remove()
的实现。注意它们都要调用两个对页表项进行操作的函数:get_pte()
和page_remove_pte()
int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) {
//pgdir是页表基址(satp),page对应物理页面,la是虚拟地址
pte_t *ptep = get_pte(pgdir, la, 1);
//先找到对应页表项的位置,如果原先不存在,get_pte()会分配页表项的内存
if (ptep == NULL) {
return -E_NO_MEM;
}
page_ref_inc(page);//指向这个物理页面的虚拟地址增加了一个
if (*ptep & PTE_V) { //原先存在映射
struct Page *p = pte2page(*ptep);
if (p == page) {//如果这个映射原先就有
page_ref_dec(page);
} else {//如果原先这个虚拟地址映射到其他物理页面,那么需要删除映射
page_remove_pte(pgdir, la, ptep);
}
}
*ptep = pte_create(page2ppn(page), PTE_V | perm);//构造页表项
tlb_invalidate(pgdir, la);//页表改变之后要刷新TLB
return 0;
}
void page_remove(pde_t *pgdir, uintptr_t la) {
pte_t *ptep = get_pte(pgdir, la, 0);//找到页表项所在位置
if (ptep != NULL) {
page_remove_pte(pgdir, la, ptep);//删除这个页表项的映射
}
}
//删除一个页表项以及它的映射
static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
if (*ptep & PTE_V) { //(1) check if this page table entry is valid
struct Page *page = pte2page(*ptep); //(2) find corresponding page to pte
page_ref_dec(page); //(3) decrease page reference
if (page_ref(page) == 0) {
//(4) and free this page when page reference reachs 0
free_page(page);
}
*ptep = 0; //(5) clear page table entry
tlb_invalidate(pgdir, la); //(6) flush tlb
}
}
//寻找(有必要的时候分配)一个页表项
pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create) {
/* LAB2 EXERCISE 2: YOUR CODE
*
* If you need to visit a physical address, please use KADDR()
* please read pmm.h for useful macros
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
* KADDR(pa) : takes a physical address and returns the corresponding
* kernel virtual address.
* set_page_ref(page,1) : means the page be referenced by one time
* page2pa(page): get the physical address of memory which this (struct
* Page *) page manages
* struct Page * alloc_page() : allocation a page
* memset(void *s, char c, size_t n) : sets the first n bytes of the
* memory area pointed by s
* to the specified value c.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry
* flags bit : Present
* PTE_W 0x002 // page table/directory entry
* flags bit : Writeable
* PTE_U 0x004 // page table/directory entry
* flags bit : User can access
*/
pde_t *pdep1 = &pgdir[PDX1(la)];//找到对应的Giga Page
if (!(*pdep1 & PTE_V)) {//如果下一级页表不存在,那就给它分配一页,创造新页表
struct Page *page;
if (!create || (page = alloc_page()) == NULL) {
return NULL;
}
set_page_ref(page, 1);
uintptr_t pa = page2pa(page);
memset(KADDR(pa), 0, PGSIZE);
//我们现在在虚拟地址空间中,所以要转化为KADDR再memset.
//不管页表怎么构造,我们确保物理地址和虚拟地址的偏移量始终相同,那么就可以用这种方式完成对物理内存的访问。
*pdep1 = pte_create(page2ppn(page), PTE_U | PTE_V);//注意这里R,W,X全零
}
pde_t *pdep0 = &((pde_t *)KADDR(PDE_ADDR(*pdep1)))[PDX0(la)];//再下一级页表
//这里的逻辑和前面完全一致,页表不存在就现在分配一个
if (!(*pdep0 & PTE_V)) {
struct Page *page;
if (!create || (page = alloc_page()) == NULL) {
return NULL;
}
set_page_ref(page, 1);
uintptr_t pa = page2pa(page);
memset(KADDR(pa), 0, PGSIZE);
*pdep0 = pte_create(page2ppn(page), PTE_U | PTE_V);
}
//找到输入的虚拟地址la对应的页表项的地址(可能是刚刚分配的)
return &((pte_t *)KADDR(PDE_ADDR(*pdep0)))[PTX(la)];
}
最后更新于