mit6.828 lab2

  • 获取lab2的代码
add git 
git pull
git checkout -b lab2 origin/lab2
git merge lab1
  • 文件说明
  1. kclock.c和kclock.h操作battery-backed clock和CMOS RAM(BIOS通过这个硬件来记录电脑中有多少物理内存)
  2. pmap.c中实现了从CMOS RAM中读取有多少内存

在这个lab中主要处理memlayout.h和pmap.h,另外熟悉inc/mmu.h也对完成这个实验大有裨益。

Part 1: Physical Page Management

现在需要去写physical page allocator,它通过一个struct PageInfo的链表来查看哪个page是可用的

Exercise 1

在kern/pmap.c中实现以下函数

boot_alloc()
mem_init() (only up to the call to check_page_free_list(1))
page_init()
page_alloc()
page_free()

线性地址结构:

// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |      Index     |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
//  \---------- PGNUM(la) ----------/

PD(page directory),PT(page table)

图e1.1
P —— Present,判断对应物理页面是否存在,存在为1,否则为0;
W —— Write,该位用来判断对所指向的物理页面是否可写,1可写,0不可写;
U —— User,该位用来定义页面的访问者应该具备的权限。为1是User权限即可,如果为 0,表示需要高特权级才能访问;
WT —— 1=Write-through,0=Write-back;
CD —— Cache Disabled,1为禁用缓存,0不禁用;
D —— Dirty,是否被修改;
A —— Accessed,最近是否被访问;
AVL —— Available,可以被系统程序所使用;
0 —— 保留位

  • boot_alloc(int n)
    实现一个memory allocator,n为要分配的字节数,该函数分配的单位为页,因此要分配n/PGSIZE + 1个页面,其中nextfree是空闲内存的虚拟地址。
	if (n == 0)
		return nextfree;

	result = nextfree;
	nextfree = ROUNDUP(nextfree, PGSIZE);  	// nextfree指向空闲内存的开始地址
	if ((uint32_t)(nextfree - KERNBASE) >= npages * PGSIZE) {
		_panic(__FILE__, __LINE__, "out of memory\n");
		return NULL;
	}
	return result;
  • mem_init()
    在这个函数中需要添加为PageInfo分配空间的代码,可以使用boot_alloc()分配内存
   // 分配PageInfo
	pages = (struct PageInfo*)boot_alloc(npages * sizeof(struct PageInfo));
	memset(pages, 0, npages * sizeof(struct PageInfo));	// init pages
  • page_init()
    这个函数用于对PageInfo的初始化。要对PageInfo进行标记,被占用的pp_ref标记为1,空闲的pp_ref标记为0。在JOS中page 0被占用,[PGSIZE, npages_basemem * PGSIZE)这段内存空闲,[IOPHYSMEM, EXTPHYSMEM)这段内存用于IO hole,需要标记为1,内存段[EXTPHYSMEM, …)前面有一部分可能是被占用的,被占用的部分可以通过(boot_alloc(0) - KERNBASE)来计算从EXTPHYSMEM被占用的页面数。对于PageInfo的空闲页使用链表维护,其中page_free_list为空闲页面链表的表头。
int alloc_num = ((uint32_t)boot_alloc(0) - KERNBASE) / PGSIZE;  // 已分配
	pages[0].pp_ref = 1;
	page_free_list = NULL;
	size_t i;
	for (i = 1; i <= npages; i++) {
		if (i <= npages_basemem || i >= EXTPHYSMEM + alloc_num) {
			pages[i].pp_ref = 0;
			pages[i].pp_link = page_free_list;
			page_free_list = &pages[i];
		}
		else {
			pages[i].pp_ref = 1;
		}
	}
  • page_alloc()
    这个函数的作用获取一个可用的page,具体操作时是返回从空闲页面的链表中取出一个空闲的PageInfo,如果alloc_flag & ALLOC_ZERO需要对返回的PageInfo置为0
struct PageInfo *
page_alloc(int alloc_flags)
{
	// Fill this function in
	if (page_free_list == NULL)
		_panic(__FILE__, __LINE__, "out of memory\n");
	struct PageInfo *res = page_free_list;
	page_free_list = page_free_list->pp_link;
	if (alloc_flags & ALLOC_ZERO)
		memset(res, 0, sizeof(struct PageInfo));
	return res;
}
  • page_free()
    这个函数的作用是返回一个PageInfo,具体操作是将这个PageInfo插入空闲页面链表。
void
page_free(struct PageInfo *pp)
{
	// Fill this function in
	// Hint: You may want to panic if pp->pp_ref is nonzero or
	// pp->pp_link is not NULL.
	if (pp->pp_ref || pp->pp_link)
		_panic(__FILE__, __LINE__, "page_free failed\n");
	pp->pp_link = page_free_list;
	page_free_list = pp;
}

Part 2: Virtual Memory

虚拟地址通过GDT获取Base再加上offset得到Linear Address,Linear Address通过Page translation得到Physical Address

Exercise 2

Page translation
在这里插入图片描述
图e2.1
页目录的物理地址存储在CR3中

Exercise 3

可以通过以下指令打开QEMU内置的monitor

qemu-system-i386 -hda obj/kern/kernel.img -monitor stdio -gdb tcp::26000 -D qemu.log  

在monitor中可以使用以下指令:

  • xp/Nx paddr – 查看paddr物理地址处开始的,N个字的16进制的表示结果。
  • info registers – 展示所有内部寄存器的状态。
  • info mem – 展示所有已经被页表映射的虚拟地址空间,以及它们的访问优先级。
  • info pg – 展示当前页表的结构
  • 在JOS的代码中,使用以下几种类型将虚拟地址和物理地址区分开来:
C type	Address type
T*  	Virtual
uintptr_t  	Virtual
physaddr_t  	Physical
  • 在以后的lab中可能会出现多个虚拟地址映射到相同的物理页中的情况,所以要在PageInfo中使用pp_ref对引用数进行维护。当一个物理页面的pp_ref为0时将这个页面free掉。需要注意的是当使用page_alloc会返回一个引用计数为0的页面,所以当使用这个页面时要将pp_ref加1。
  • 当进入保护模式后,没有办法直接办法使用线性地址或者物理地址,所用内存引用使用的都是虚拟内存都要被MMU解析,这也意味着C语言中的所有指针中的值都是虚拟地址。
  • Question
Assuming that the following JOS kernel code is correct, what type should variable x have, uintptr_t or physaddr_t?
	mystery_t x;
	char* value = return_a_pointer();
	*value = 10;
	x = (mystery_t) value;

答:mystery_t是uintptr_t,因为physaddr_t不可以被解引用。

  • JOS中的物理地址从0开始,虚拟地址从0xf0000000开始
// 获取高位,获取PDE和PTE中的物理地址
#define PTE_ADDR(pte)	((physaddr_t) (pte) & ~0xFFF)

// paddr->vaddr
static inline physaddr_t
_paddr(const char *file, int line, void *kva)
{
	if ((uint32_t)kva < KERNBASE)
		_panic(file, line, "PADDR called with invalid kva %08lx", kva);
	return (physaddr_t)kva - KERNBASE;
}

// vaddr->paddr
static inline void*
_kaddr(const char *file, int line, physaddr_t pa)
{
	if (PGNUM(pa) >= npages)
		_panic(file, line, "KADDR called with invalid pa %08lx", pa);
	return (void *)(pa + KERNBASE);
}

dizhi
每个页表都占一个页

  • pgdir_walk()
    此函数的作用是根据pgdir和va返回对应的page table entry的指针,需要注意的是如上图所示page directory entry和page table entry中存储的都是物理地址,因为进入保护模式后JOS中的指针都要使用虚拟地址,所以从page directory中获取pte_t时需要将pde中的PPN部分提取出来后再转为物理地址(PPN << 12)再转为虚拟地址才可以访问page table(PPN物理页索引)。由pte转化为物理地址的具体方式为,PTE中的PPN << 20 + laddr.offset。从pte中可以知道page table是否存在,如果不存在则需要为该page table分配一块页表,并更新pde的信息,最后返回对应的pte_t
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
	// Fill this function 
	int pd_idx = PDX(*(uintptr_t*)va);  // 获取页目录的offset
	int pt_idx = PTX(*(uintptr_t*)va);

	pte_t *pd_entry = pgdir[pd_idx];

	if (*pd_entry & PTE_P) {	// 如果页表页存在
		pte_t *target_pt = KADDR(PTE_ADDR(pd_entry));
		return target_pt + pt_idx;
	}
	else {	// 如果页表页不存在
		if (create == false)
			return NULL;
		else {
			struct PageInfo *new_page = page_alloc(ALLOC_ZERO); // 分配一个页表页
			if (new_page == NULL)
				return NULL;
			new_page->pp_ref++;	
			physaddr_t new_page_pyaddr = page2pa(new_page);	// 获取但此上游分支已经不存在。页面的paddr
			pd_entry[pt_idx] = new_page_pyaddr | PTE_P | PTE_W | PTE_U;	// 构建pde
			pte_t *target_pt = KADDR(new_page_pyaddr);	// 获取page table的vaddr
			return target_pt + pt_idx;	// 返回pte的vaddr
		}
	}
}
  • boot_map_region()
    该函数的作用是实现从虚拟地址空间[va, va+size)到物理空间[pa, pa+size)的映射,其中size为page的倍数,va和pa时page-aligned的,所以va和pa的低12位为0。实现映射实际上是在page table entry将PPN改为物理地址的高20位,另外还需要更改pte中的flags。
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
	// Fill this function in
	int page_num = size / PGSIZE;
	int i = 0;
	pte_t* pt_vaddr;
	// 将映射的数据分割为页,更新页的结构
	for (i = 0; i < page_num; i++) {
		pte_t *pte_item = pgdir_walk(pgdir, va, 1);	// 获取page table entry
		*pte_item = pa | perm | PTE_P; // 更新pte信息,因为pa是page-aligned的,所以pa前12位为0
		va += PGSIZE;
		pa += PGSIZE;
	}

}
  • page_lookup()
    此函数的作用是根据va返回对应的PageInfo,所以当对应的pde或者pte中的PTX_P为0时都说明va所对应的物理页不存在,需要返回NULL。如果pte_store不为空,将pte存储在pte_store中。最后获取pte中PPN对应的物理地址,根据物理地址获取PageInfo将其返回。
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
	// Fill this function in
	pte_t *pte_item = pgdir_walk(pgdir, va, 0);
	if (pte_item == NULL || (*pte_item) & PTE_P) // pte不存在或者pte所对应的物理页不存在
		return NULL;

	if (pte_store != NULL)	// 如果pte_store不为空将pte存储在pte_store中
		*pte_store = pte_item;

	return pa2page(PTE_ADDR(*pte_item));
}
  • page_remove()
    该函数的作用是解除va与对应物理页的映射关系,具体步骤为:
  1. 解引用,如果物理页面的引用数为0,则释放该物理页
  2. 将对应的pte设为0
  3. 使对应的tlb无效
void
page_remove(pde_t *pgdir, void *va)
{
	// Fill this function in
	pte_t *pte_item = NULL;
	struct PageInfo *curr_page = page_lookup(pgdir, va, &pte_item);
	if (curr_page == NULL)
		return;
	*pte_item = 0;
	
	page_decref(curr_page);

	tlb_invalidate(pgdir, va);
}

invlpg指令(tlb_invalidate函数中使用)
在这里插入图片描述

  • page_insert()
    该函数的作用为更新vaddr和物理页的关系,先获取va对应的pte,如果pte对应的paddr和pp对应的paddr相同,则只需更新perm,pp_ref不变。如果pte所对应的物理页面和pp对应的不是同一个物理页面,则取消掉va与该物理页面的映射关系,并将pte与pp建立相关关系,pp++,更新perm。
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
	// Fill this function in
	pte_t *pte_item = NULL;
	pte_item = pgdir_walk(pgdir, va, 1);
	if (pte_item == NULL)
		return -E_NO_MEM;

	if (*pte_item & PTE_P) {
		if (page2pa(pp) == PTE_ADDR(*pte_item)) {  // 如果当前页面和原来的页面映射到同一个paddr
			*pte_item |= perm | PTE_P;
			return 0;
		}
		else 
			page_remove(pgdir, va);
	}
	pp->pp_ref++;
	pte_item = page2pa(pp) | perm | PTE_P;
	return 0;
}

Part 3: Kernel Address Space

memory layout:
在这里插入图片描述
用户态无权对ULIM以上的内存做任何操作,只有在内核中才能对这段内存进行读写。在内存段[UTOP, ULIM)中,无论是用户态还是内存态都只有读的权限。低于UTOP的内存段是给用户态使用的。

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:
EntryBase Virtual AddressPoints to (logically)
10230xffc00000Page table for top 4MB of phys memory
10220xff800000Remapped physical memory
.??
.??
.??
20x00800000Program data and heap
10x00400000Empty memory
00x00000000Empty memory

Base Virtual Address = Entry << 22 (Entry相当于Base Virtual Address的高10位)

  1. We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel’s memory? What specific mechanisms protect the kernel memory?
    答处于用户态中的程序不能访问修改内核中的代码和数据,否则会搞崩内核。在JOS中主要是通过页目录和页表中的flags来控制对内存的访问权限的。

  2. What is the maximum amount of physical memory that this operating system can support? Why?
    答:在JOS中使用以UPAGES为起始地址,1PTSIZE(1024 * 4096) = 4MB大小的内存段来存储PageInfo结构体,一个PageInfo 8B,所以在JOS中最多可存储的PageInfo个数为4MB / 8B = 512K个PageInfo,也就是说最多支持512K个物理页(每个物理页4KB),所以JOS中最多可以支持使用的物理内存为4KB * 512K = 2GB。

  3. 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?
    答:一个页表为4KB,一个PTE 4B,所以一个页表可支持1K个物理页,当使用的内存达到2G时需要的页表数目为512K/1K = 512。页表加上页目录的开销为513 * 4KB = 2052KB,再加上用于存储PageInfo的4MB,所以需要的额外开销为2052KB + 4MB = 6148KB。

  4. Revisit the page table setup in kern/entry.S and kern/entrypgdir.c. Immediately after we turn on paging, EIP is still a low number (a little over 1MB). At what point do we transition to running at an EIP above KERNBASE? What makes it possible for us to continue executing at a low EIP between when we enable paging and when we begin running at an EIP above KERNBASE? Why is this transition necessary?
    答:在entry.S中的jmp *%eax指令是跳到一个比KERNBASE更高的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值