【MIT CS6.828】Lab 2: Memory Management - Part 3: Kernel Address Space

1. 建立内核虚拟地址的映射

在 Part 3,我们需要建立虚拟地址空间中内核部分的映射。


Exercise 5. Fill in the missing code in mem_init() after the call to check_page().

Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.


分为三个部分:

  1. 建立用户空间虚拟地址UPAGES到物理地址pages的映射,大小应为即pages的大小npages * sizeof(struct PageInfo) (0x40000),但在inc/memlayout.h的图中,R0 PAGES 所占地址空间大小为PTSIZE,所以应该设为PTSIZE。(但 0x40000 和 PTSIZE 都能通过测试)

  2. 建立内核空间栈的虚拟地址KSTACKTOP到物理地址bootstack(在entry.S中定义的)的映射,注意地址是递减的。映射的地址空间大小(大于KSTKSIZE)被规定为PTSIZE,即一个页目录项可映射到的所有页面的总字节数(1024页*4KB)。

    但还需要特殊处理,将 [KSTACKTOP-PTSIZE, KSTACKTOP) 分为两部分进行映射,以KSTACKTOP-KSTKSIZE为界。其中 [KSTACKTOP-KSTKSIZE, KSTACKTOP) 部分映射到物理地址,而 [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) 不进行映射。

    这样做,可以确保栈向低地址增长时始终与其他数据保持着一定的空白区域,一旦栈溢出,也只会溢出到这片区域内,而不会覆盖掉其他数据。

  3. 建立虚拟地址 [KERNBASE, 2^32) 到全部物理地址 [0, 2^32 - KERNBASE) 的映射,大小为 0xffffffff - 0xf0000000。当然,实际物理内存并没有这么大,但不管怎样就是这么设了……注释也没说为什么。

// UPAGES
boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);
// kernel stack
boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);
// all of physical memory
boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);

到这里pmap.c全部完成。

running JOS: (1.1s) 
  Physical page allocator: OK 
  Page management: OK 
  Kernel page directory: OK 
  Page management 2: OK 
Score: 70/70

现在来看一下建立上述映射后kern_pgdir的具体情况。在mem_init中加一点打印辅助:

void
mem_init(void)
{
    ...
    check_kern_pgdir();
	...
    // 遍历kern_pgdir,打印每个PDE对应页表的第一项对应的虚拟地址
    int i;
    for(i = 0; i < 1024; i++){ // 共1024个页目录项
        if(!kern_pgdir[i]) continue;
        pte_t* first_pte = KADDR(PTE_ADDR(kern_pgdir[i]));
        physaddr_t pa = PTE_ADDR(*first_pte);
        // 使用页目录索引、页表索引、物理地址低12bit,拼接得到虚拟地址
        pte_t* va = PGADDR(i, 0, (pa & 0xfff));
        cprintf("kern_pgdir[%d]: 0x%x\n", i, va);
    }
}

输出结果:

kern_pgdir[956]: 0xef000000  # UPAGES,占一个页目录项
kern_pgdir[957]: 0xef400000  # UVPT
kern_pgdir[959]: 0xefc00000 # stack,占一个页目录项
kern_pgdir[960]: 0xf0000000  # KERNBASE
kern_pgdir[961]: 0xf0400000
...
kern_pgdir[1021]: 0xff400000
kern_pgdir[1022]: 0xff800000
kern_pgdir[1023]: 0xffc00000

2. 页的权限管理

参考:Intel 80386 Reference Programmer's Manual Table of Contents 6.4 Page-Level Protection

前面提过,页表项结构如下图所示:

image

其中:

  • U位:1-用户级,0-特权级。用户态下仅能寻址用户级页面,内核态下能寻址所有页面。用户态/内核态是由 CPL (当前特权级)字段的值决定的,CPL保存在寄存器中。
  • W位:1-可读可写,0-只读。用户态下只能写那些可读可写的用户级页面,且所有的特权级界面都不可读不可写。内核态下可以读写所有页面。

这两位结合可以实现对页的各类权限设置。

3. Questions


Question

  1. What entries (rows) in the page directory have been filled in at this point? What addresses do they map and where do they point? In other words, fill out this table as much as possible:

    对照前面的打印信息及memlayout.h中的虚拟地址布局填写。

    Entry Base Virtual Address Points to (logically):
    1023 0xffc00000 Page table for top 4MB of phys memory
    ... ... ...
    961 0xf0400000 Page table for [4,8) MB of phys memory
    960 0xf0000000 Page table for [0,4) MB of phys memory
    959 0xefc00000 内核栈空间(含无映射部分)
    ... ... ...
    957 0xef400000 UVPT
    956 0xef000000 UPAGES
    ... ... ...
  2. 我们已经将内核和用户环境放在同一个地址空间中。为什么用户程序无法读取或写入内核内存?哪些具体机制保护内核内存?

    内核内存相关页面的页表项的U位置0,设为特权级页面,拒绝用户态下对这些页面的寻址,因此用户程序无法读取或写入内核内存。

    机制已在前文说明。

  3. 该操作系统可以支持的最大物理内存量是多少?为什么?

    2GB。UPAGE空间为 4MB,即数组pages最多可以有 4MB/8B = 512K 个元素,每个物理页面 4KB,则 512K 个物理页面总大小为 512*1024*4*1024 B = 2 GB。

  4. How much space overhead is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

    达到最大物理内存时,pages需要 4MB,512K 个物理页面对应 512K 个页表项,每个页表项 4B,需要 2MB 存储全部页表项。另页目录表kern_pgdir占4KB(实际上貌似2KB就够用了)。故内存管理开销为 6MB + 4KB.

  5. 重新查看kern/entry.Skern/entrypgdir.c中的页表设置。在我们开启分页后,EIP 仍然是一个较低的数字(略高于 1MB)。我们在什么时候切换到在 KERNBASE 之上的 EIP 上运行?从启用分页到开始使用高于 KERNBASE 的 EIP 这一段时间内,为什么我们能够继续使用低 EIP ?为什么这种切换是必要的?

    kern/entry.Sjmp *%eax处切换。entrypgdir.c硬编码了一个静态页表,建立了虚拟地址 [0, 4M) 到物理地址 [0, 4M) 的映射,因此在切换之前低 EIP 可用。必须进行切换,因为内核倾向于运行在很高的地址空间,以保证内核的虚拟地址空间有足够的冗余来同时映射到它下面的用户环境中(?不是很确定)

4. 挑战:扩展 JOS monitor

Lab 2 留了几道挑战题,我菜,挑个最简单的……


Challenge! Extend the JOS kernel monitor with commands to:

  • Display in a useful and easy-to-read format all of the physical page mappings (or lack thereof) that apply to a particular range of virtual/linear addresses in the currently active address space. For example, you might enter showmappings 0x3000 0x5000to display the physical page mappings and corresponding permission bits that apply to the pages at virtual addresses 0x3000, 0x4000, and 0x5000.
  • Explicitly set, clear, or change the permissions of any mapping in the current address space.
  • Dump the contents of a range of memory given either a virtual or physical address range. Be sure the dump code behaves correctly when the range extends across page boundaries!
  • Do anything else that you think might be useful later for debugging the kernel. (There's a good chance it will be!)

int 
mon_showmappings(int argc, char **argv, struct Trapframe *tf)
{
	if(argc == 1 || argc > 3) {
		cprintf("Usage: showmappings <begin_addr> <end_addr>\n");
		return 0;
	}
	uint32_t begin_addr = xtoi(argv[1]);
	uint32_t end_addr = xtoi(argv[2]);
	// cprintf("begin: %x, end: %x\n", begin_addr, end_addr);
	int i;
	for(i = begin_addr; i <= end_addr; i+=PGSIZE){
		pte_t* pte = pgdir_walk(kern_pgdir, (void *)i, 0);
		if(!pte || !(*pte & PTE_P)) {
			cprintf("missing physical page\n");
			continue;
		}
		else {
			cprintf("page %x ", KADDR(PTE_ADDR(*pte)));
			cprintf("PTE_P: %d, PTE_W: %d, PTE_U: %d", *pte & PTE_P, (*pte & PTE_W) >> 1, (*pte & PTE_U) >> 2);
			cprintf("\n");
		}
	}
	return 0;
}
int 
mon_change_mappings_permission(int argc, char** argv, struct Trapframe *tf)
{
	if(argc == 1 || argc > 4) {
		cprintf("Usage: chmapper <addr> <set|clear> <PTE_W|PTE_U>\n");
		return 0;
	}
	uint32_t addr = xtoi(argv[1]);
	pte_t* pte = pgdir_walk(kern_pgdir, (void *)addr, 0);
	if(!pte || !(*pte & PTE_P)){
		cprintf("missing phiscal page\n");
		return 0;
	}
	if(strcmp(argv[3], "PTE_W") == 0){
		if(strcmp(argv[2], "set") == 0){
			*pte |= PTE_W;
		}
		else if(strcmp(argv[2], "clear") == 0){
			*pte = *pte & (~PTE_W);
		}
	}
	else if(strcmp(argv[3], "PTE_U") == 0){
		if(strcmp(argv[2], "set") == 0){
			*pte |= PTE_U;
		}
		else if(strcmp(argv[2], "clear") == 0){
			*pte = *pte & (~PTE_U);
		}
	}
	else{
		cprintf("Usage: chmapper <addr> <set|clear> <PTE_W|PTE_U>\n");
		return 0;
	}
	return 0;
}
K> showmappings ef000000 ef005000
page f011c000 PTE_P: 1, PTE_W: 0, PTE_U: 1
page f011d000 PTE_P: 1, PTE_W: 0, PTE_U: 1
page f011e000 PTE_P: 1, PTE_W: 0, PTE_U: 1
page f011f000 PTE_P: 1, PTE_W: 0, PTE_U: 1
page f0120000 PTE_P: 1, PTE_W: 0, PTE_U: 1
page f0121000 PTE_P: 1, PTE_W: 0, PTE_U: 1
K> chmapper ef000000 set PTE_W
K> showmappings  ef000000  ef000000
page f011c000 PTE_P: 1, PTE_W: 1, PTE_U: 1
K> chmapper ef000000 clear PTE_W
K> showmappings  ef000000  ef000000
page f011c000 PTE_P: 1, PTE_W: 0, PTE_U: 1

参考资料

  1. xv6 - Chapter 2 Page tables
  2. https://github.com/Clann24/jos/tree/master/lab2
posted @ 2023-02-07 18:19  StreamAzure  阅读(62)  评论(0编辑  收藏  举报