简而言之,使用pageinfo结构体映射物理页,使用pages数组保存页结构体,pagefreelist保存空闲页结构体,使用pa2page和page2pa实现物理页和页结构体的转换,使用二级页表机制进行物理地址与虚拟地址的转换,使用PDX、PTX等宏(其实就是简单的移位操作)实现虚拟地址查页表,各级页表的页表项结构相同,都是前20位物理页索引,后12位标志位。
更详细的页表项、结构说明等,参见lab2_exercise1讲解。
手动实现了10个函数
- boot_alloc()
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
if (n == 0) return nextfree;
else if (n > 0) {
result = nextfree;
nextfree += ROUNDUP(n, PGSIZE); //如果n大于零,就这么搞。注意这里res和nex的用法,nex始终指向
return result; //被分配出来的空白空间的开头,而nex始终指向被分配空间的结尾
} //就象这里,可以对指针直接加整型数,来表示指针的移动和分配空间
else return NULL;
}
**在运行虚拟内存系统之前暂时分配页。**实际是使用一个指针,保存一个uint_32_t类型的值,直接将这个值作为指针(地址)。将n向上取整为PGSIZE的倍数后,nextfree后移。
- mem_int()1
pages = (struct PageInfo*) boot_alloc(sizeof(struct PageInfo) * npages);
memset(pages, 0, sizeof(struct PageInfo) * npages);
**初始化页表结构体数组pages,pages中的每一项对应一个物理页,pages用来保存页表结构体。**通过页表结构体,就能索引到页表,以及当前页的其他信息。
- page_init()
void
page_init(void)
{
size_t i;
for (i = 1; i< npages_basemem; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
for (i = PGNUM(PADDR(boot_alloc(0))); i < npages; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
**初始化页表结构,包括结构体赋初值和空闲页表维护。**使用单链表连接空页。用物理内存的高位来映射在pages中的页号。
- page_alloc()
struct PageInfo *
page_alloc(int alloc_flags)
{
if (page_free_list == NULL) return NULL;
struct PageInfo* res = page_free_list;
page_free_list = page_free_list->pp_link;
res->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO)
memset(page2kva(res), '\0', PGSIZE);
return res;
}
分配出一个空闲页,返回这个页的结构体。
- page_free()
void
page_free(struct PageInfo *pp)
{
if (pp->pp_ref || pp->pp_link) panic("Page isn't empty!\n");
pp->pp_link = page_free_list;
page_free_list = pp;
}
**删除一个空闲页。**只有当这个页是空时才能删除。将这个页重新挂在pagefreelist单链表上。
- pgdir_walk()
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
pde_t* tpde = &pgdir[PDX(va)]; //PDX(va)是含va的二级页表在一级页表的索引,即指向va的一级页表项。pgdir是一个页表目录,就是一级页表的初址
pte_t* tpte=NULL; //保存包含va的二级页表首地址
if (*tpde & PTE_P) { //如果一级页表中有含va的一级页表项,且这个二级页表页可以分配,
tpte = (pte_t*) KADDR(PTE_ADDR(*tpde)); //就将tpte指向这个二级页表
}
else { //如果一级页表中对应项没有已分配的二级页表页
if (!create) return NULL; //如果不可创建,返回空
else { //如果可以创建
struct PageInfo * newpage = page_alloc(true); //创建一个二级页表页
if (!newpage) return NULL; //如果创建失败
newpage->pp_ref++;
*tpde = page2pa(newpage) | PTE_P | PTE_U | PTE_W; //创建之后,将这个对应的创建的页标志位设为present、user、write
tpte = page2kva(newpage); //tpte指向新创建的二级页表页
}
}
return &tpte[PTX(va)]; //PTX得到va在二级页表页内的偏移,以tpte初址寻址后,得到含va的二级页表项,取地址,得到指向va所在的二级页表项的指针。
}
返回指向一个虚拟地址va的二级页表项的指针(没有这个项则按create决定是否创建)。
tpte无论哪种情况,都要转化成虚拟地址,因为此时页表机制已经启动,地址都会通过页表机制进行转化,这样最后才能通过数组索引,找到对应项取地址。
- boot_map_region()
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
for(size_t i = 0; i < size; i += PGSIZE){
pte_t* pte = pgdir_walk(pgdir, (const void *)va + i, 1);
*pte = (pa + i) | PTE_P | perm;
}
}
**将虚拟地址va处的size字节,映射到物理地址pa处。**由于已经对齐了,所以这里直接调函数赋值就行了。
- page_insert()
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
pte_t * tpte = pgdir_walk(pgdir, va, true);
if (!tpte) return -E_NO_MEM;
pp->pp_ref++;
if (*tpte & PTE_P) page_remove(pgdir, va);
*tpte = page2pa(pp) | perm | PTE_P;
return 0;
}
**将虚拟地址va插入页结构体pp对应页中。**比较好理解,只需要找到va对应的二级页表项,将这一项赋值为pp对应页的物理地址,再设置权限位即可。
- page_lookup()
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
pte_t* ppte = pgdir_walk(pgdir, va, 0);
if (!ppte) return NULL;
pte_t idx = *ppte>>PTXSHIFT<<PTXSHIFT;
if (pte_store != 0) *pte_store = ppte;
return pa2page(idx);
}
**查找到va对应页的页结构体。**详细解释在exercise4讲解中。
- page_remove()
void
page_remove(pde_t *pgdir, void *va)
{
pte_t * ppp;
struct PageInfo * pg1 = page_lookup(pgdir, va, &ppp);
if (!pg1) return;
*ppp = 0;
pg1->pp_ref--;
if (pg1->pp_ref == 0) page_free(pg1);
tlb_invalidate(pgdir, va);
}
删除va与现在它所在的页表的映射。