页表管理
- 在
kern/pmap.c
中,实现如下函数的代码:- pgdir_walk()
- boot_map_region()
- page_lookup()
- page_remove()
- page_insert()
- 之后 mem_init() 中的 check_page() 会测试这些页表管理程序
首先是 pgdir_walk()
实现的功能是:给定一个页目录表指针 pgdir ,该函数应该返回线性地址va所对应的页表项指针。
结合注释来一步步完成这个函数:
pgdir_walk()
// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
// a pointer to the page table entry (PTE) for linear address 'va'.
// This requires walking the two-level page table structure.
// 这部分说明了该函数的作用,并且提示我们需要经过二级转换才能得到最终指向PTE的指针。
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, then pgdir_walk returns NULL.
// 如果页所在的页表不存在,且不创建新的页,返回NULL
// Otherwise, pgdir_walk allocates a new page table page with page_alloc.
// - If the allocation fails, pgdir_walk returns NULL.
// - 创建新页失败了,也返回NULL
// - Otherwise, the new page's reference count is incremented,
// - 创建成功,需要给新页的引用计数pp_ref立即+1
// the page is cleared,
// 要给页面清零
// and pgdir_walk returns a pointer into the new page table page.
//
// Hint 1: you can turn a PageInfo * into the physical address of the
// page it refers to with page2pa() from kern/pmap.h.
// 需要用到page2pa将一个 struct PageInfo * 转换为物理地址 pa
//
// Hint 2: the x86 MMU checks permission bits in both the page directory
// and the page table, so it's safe to leave permissions in the page
// directory more permissive than strictly necessary.
// 最好是在页目录里面,给页目录项的权限更高一点,不要限制的太过头
//
// Hint 3: look at inc/mmu.h for useful macros that manipulate page
// table and page directory entries.
// 查看mmu.h里面有关于操纵PTE和PDE的函数
// ------------------inc/mmu.h------------------ //
// 1.
// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory | Page Table | Offset within Page |
// | Index | Index | |
// +----------------+----------------+---------------------+
// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
// \---------- PGNUM(la) ----------/
//
// The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
// use PGADDR(PDX(la), PTX(la), PGOFF(la)).
// 由于没有分段机制 va = la,因此以上三个函数可以直接作用在va上,求得页目录索引、页表索引、页内偏移。
// 在得知三者的情况下,也可以用 PGADDR(PDX(la), PTX(la), PGOFF(la)) 来解析出 va
// 2.
// Page table/directory entry flags.
#define PTE_P 0x001 // Present
#define PTE_W 0x002 // Writeable
#define PTE_U 0x004 // User
#define PTE_PWT 0x008 // Write-Through
#define PTE_PCD 0x010 // Cache-Disable
#define PTE_A 0x020 // Accessed
#define PTE_D 0x040 // Dirty
#define PTE_PS 0x080 // Page Size
#define PTE_G 0x100 // Global
// 页表/页目录项的权限标志位
// 3.
// Address in page table or page directory entry
#define PTE_ADDR(pte) ((physaddr_t) (pte) & ~0xFFF)
// 页表/页目录项(pte) 记载的物理地址
// ------------------inc/mmu.h------------------ //
//
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
assert(pgdir != NULL);
pde_t *pg_dir_entry = NULL; // 指示页目录索引的位置
pte_t *pg_table = NULL; // 指示页表索引的位置
struct PageInfo *new_page = NULL; // 创建的新页
pg_dir_entry = &pgdir[PDX(va)]; // 通过PDX(va)求出页目录的索引值
if (!(*pg_dir_entry & PTE_P)) { // PTE_P 指示目录项是否存在
if (!create) {
return NULL;
} else {
new_page = page_alloc(ALLOC_ZERO);
if (new_page == NULL) {
return NULL;
} else {
new_page->pp_ref++; // 引用计数立即+1
*pg_dir_entry = (page2pa(new_page) | PTE_P | PTE_W | PTE_U); // 求出页面的物理地址并更改权限
}
}
}
pg_table = KADDR(PTE_ADDR(*pg_dir_entry)); // 取出页目录项中存放的页表的物理地址,而后转换为页表的虚拟地址赋予pg_table。!!!转成虚拟地址是因为c语言中的指针都是存放的虚拟地址!!!
return &pg_table[PTX(va)]; // 由PTX(va) 求出虚拟地址对应的页表索引值,返回该索引值对应的页表项的地址
// return NULL;
}
// 疑问:
// 为什么通过page2pa求出物理页地址后,要将它的地址添加到页目录项中?不是应该添加到页表项中吗?
接下来完成静态的地址映射,主要是通过 boot_map_region()
来实现,设置虚拟地址在UTOP之上的地址范围。由于是静态的,在操作系统的运行过程中不会改变,所以这个页的PageInfo的pp_ref字段的值不会发生变化。
boot_map_region()
//
// Map [va, va+size) of virtual address space to physical [pa, pa+size)
// in the page table rooted at pgdir. Size is a multiple of PGSIZE, and
// va and pa are both page-aligned.
// Use permission bits perm|PTE_P for the entries.
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//
// Hint: the TA solution uses pgdir_walk
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
pte_t *pg_table_entry = NULL;
uint32_t page_num = size / PGSIZE;
for (uint32_t i = 0; i < page_num; i++) {
pg_table_entry = pgdir_walk(pgdir, (const void *)va, 1); // 求得va对应的页表项
assert(pg_table_entry != NULL);
*pg_table_entry = pa | perm | PTE_P; // 设置页表项存储的物理页地址和权限flags
va += PGSIZE;
pa += PGSIZE;
}
}
page_insert()
功能:把一个物理内存中页pp与虚拟地址va建立映射关系。
//
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table entry
// should be set to 'perm|PTE_P'.
//
// Requirements
// - If there is already a page mapped at 'va', it should be page_remove()d.
// - If necessary, on demand, a page table should be allocated and inserted
// into 'pgdir'.
// - pp->pp_ref should be incremented if the insertion succeeds.
// - The TLB must be invalidated if a page was formerly present at 'va'.
//
// Corner-case hint: Make sure to consider what happens when the same
// pp is re-inserted at the same virtual address in the same pgdir.
// However, try not to distinguish this case in your code, as this
// frequently leads to subtle bugs; there's an elegant way to handle
// everything in one code path.
//
// RETURNS:
// 0 on success
// -E_NO_MEM, if page table couldn't be allocated
//
// Hint: The TA solution is implemented using pgdir_walk, page_remove,
// and page2pa.
//
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
// Fill this function in
pte_t *pg_table_entry = pgdir_walk(pgdir, (const void *)va, 1); // 求出va对应的页表项,因为"a page table should be allocated",所以调用pgdir_walk时create=1,也就是没有页时要创建页
if (pg_table_entry == NULL) {
return -E_NO_MEM;
}
pp->pp_ref++; // pp的引用计数+1
if (*pg_table_entry & PTE_P) { // 如果页表项之前有别的物理页已经映射到va
tlb_invalidate(pgdir, va); // 修改TLB
page_remove(pgdir, va); // 将之前的页表项中存储的物理页去除
}
*pg_table_entry = page2pa(pp) | perm | PTE_P; // 设置标志位并将物理页地址存入页表项
pgdir[PDX(va)] |= perm; // ???
return 0;
}
// pp->pp_ref++这条语句,一定要放在page_remove之前。这是为了处理pa已经映射到va
// 如果放在之后的话,如果此va对应的pp的引用计数是1,那么page_remove之后这个页面资源就会被释放掉了。单给pp->pp_ref++, pp->pp_link依然=NULL,并没有指涉任何一个物理页
page_lookup()
功能:查找va映射到的物理页。
//
// Return the page mapped at virtual address 'va'.
// If pte_store is not zero, then we store in it the address
// of the pte for this page. This is used by page_remove and
// can be used to verify page permissions for syscall arguments,
// but should not be used by most callers.
//
// Return NULL if there is no page mapped at va.
//
// Hint: the TA solution uses pgdir_walk and pa2page.
//
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
// Fill this function in
pte_t *pg_table_entry = pgdir_walk(pgdir, va, 0); // 因为是lookup,所以不创建新的页面
if (pg_table_entry == NULL) {
return NULL; // 如果页表项不存在,返回NULL
}
if (!(*pg_table_entry & PTE_P)) {
return NULL; // 如果页表项没有绑定物理页,返回NULL
}
struct PageInfo *result = NULL;
result = pa2page(PTE_ADDR(*pg_table_entry)); // 求出页表项保存的物理页地址并转换为PageInfo
if (pte_store != NULL) {
pte_store = &pg_table_entry; // pte_store保存页表项指针的地址
}
return result;
// return NULL;
}
page_remove()
功能:移除va和物理页之间的映射关系。
//
// Unmaps the physical page at virtual address 'va'.
// If there is no physical page at that address, silently does nothing.
//
// Details:
// - The ref count on the physical page should decrement.
// - The physical page should be freed if the refcount reaches 0.
// - The pg table entry corresponding to 'va' should be set to 0.
// (if such a PTE exists)
// - The TLB must be invalidated if you remove an entry from
// the page table.
//
// Hint: The TA solution is implemented using page_lookup,
// tlb_invalidate, and page_decref.
//
void
page_remove(pde_t *pgdir, void *va)
{
// Fill this function in
pte_t *pg_table_entry = NULL;
struct PageInfo *page = page_lookup(pgdir, va, &pg_table_entry);
if (page == NULL) {
return ;
}
page_decref(page); // 实现上面的Details 1&2
tlb_invalidate(pgdir, va); // 实现上面的Details 4
*pg_table_entry = 0; // 实现上面的Details 3,即此va对应的页表项保存的值为0,此时PTE_P标志位为0,代表没有绑定任何物理页
}