MIT6.828-Lab2-Part2-Exercise4-实现pmap.c中管理页表的程序

 

页表管理

  1. kern/pmap.c 中,实现如下函数的代码:
    1. pgdir_walk()
    2. boot_map_region()
    3. page_lookup()
    4. page_remove()
    5. page_insert()
  2. 之后 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,代表没有绑定任何物理页
}

参考资料

  1. cnblog-fatsheep9146
  2. csdn-bysui