MIT-6.828 Lab 2: Memory Management实验报告

tags:mit-6.828 os


概述

本文主要介绍lab2,讲的是操作系统内存管理,从内容上分为三部分:

  1. 第一部分讲的是物理内存管理,要进行内存管理首先需要知道哪些物理内存是空闲的,哪些是被使用的。还需要实现一些函数对这些物理内存进行管理。
  2. 第二部分讲的是虚拟内存。一个虚拟地址如何被映射到物理地址,将实现一些函数来操作页目录和页表从而达到映射的目的。
  3. 第三部分讲的是内核的地址空间。将结合第一部分和第二部分的成果,来对内核地址空间进行映射。

Part 1: Physical Page Management

通过lab1可以总结出如下的物理内存分布图:



大致上可以分为三部分:

  1. 0x00000~0xA0000:这部分叫做basemem,是可用的。
  2. 接着是0xA0000~0x100000:这部分叫做IO Hole,不可用。
  3. 再接着就是0x100000以上的部分:这部分叫做extmem,可用。

    kern/pmap.c中的i386_detect_memory()统计有多少可用的物理内存,将总共的可用物理内存页数保存到全局变量npages中,basemem部分可用的物理内存页数保存到npages_basemem中。

Exercise 1:

需要我们写一个物理内存页的allocator。要求实现kern/pmap.c文件中的boot_alloc(),mem_init(),page_init(),page_alloc(),page_free()。check_page_free_list()和check_page_alloc()中会有一些测试用例,如果没有通过两个函数则说明代码有问题,一种类似TDD的开发流程。

从lab1知道,进入内核后首先调用的是i386_init(),该函数会调用mem_init()。mem_init()调用其他工具函数实现内核内存管理。该函数首先调用i386_detect_memory()来计算有多少可以的物理内存页保存到npages和npages_basemem中。然后调用boot_alloc()。

boot_alloc()实现如下:

static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result; // Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[]; //在/kern/kernel.ld中定义的符号,位于bss段的末尾
nextfree = ROUNDUP((char *) end, PGSIZE);
} // Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
result = nextfree;
nextfree = ROUNDUP((char *)result + n, PGSIZE);
cprintf("boot_alloc memory at %x, next memory allocate at %x\n", result, nextfree);
return result;
}

该函数维护一个static的指针nextfree,初始值是end,end是定义在/kern/kernel.ld中定义的符号,位于bss段的末尾。也就是说从内核的末尾开始分配物理内存。需要添加的代码是:

	result = nextfree;
nextfree = ROUNDUP((char *)result + n, PGSIZE);
cprintf("boot_alloc memory at %x, next memory allocate at %x\n", result, nextfree);
return result;

每次调用都返回nextfree,然后根据参数n更新nextfree的值,使其指向下一个空闲地址处。

mem_init()调用boot_alloc(),将返回值赋给全局变量kern_pgdir,kern_pgdir保存的是内核页目录的物理地址。

接着根据mem_init()中的注释:

	// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
// The kernel uses this array to keep track of physical pages: for
// each physical page, there is a corresponding struct PageInfo in this
// array. 'npages' is the number of physical pages in memory. Use memset
// to initialize all fields of each struct PageInfo to 0.

在mem_init()中补充如下代码:

	pages = (struct PageInfo*)boot_alloc(sizeof(struct PageInfo) * npages);	//分配足够大的空间(PGSIZE的倍数)保存pages数组
memset(pages, 0, sizeof(struct PageInfo) * npages);

这段代码分配足够的的内存空间保存pages数组,pages数组的每一项是一个PageInfo结构,对应一个物理页的信息,定义在inc/memlayout.h中。

接下来mem_init()调用page_init()。

page_init()实现如下:

// --------------------------------------------------------------
// Tracking of physical pages.
// The 'pages' array has one 'struct PageInfo' entry per physical page.
// Pages are reference counted, and free pages are kept on a linked list.
// -------------------------------------------------------------- //
// Initialize page structure and memory free list.
// After this is done, NEVER use boot_alloc again. ONLY use the page
// allocator functions below to allocate and deallocate physical
// memory via the page_free_list.
//
void
page_init(void)
{
// The example code here marks all physical pages as free.
// However this is not truly the case. What memory is free?
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don't, but...)
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
// 4) Then extended memory [EXTPHYSMEM, ...).
// Some of it is in use, some is free. Where is the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
//
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
// 这里初始化pages中的每一项,建立page_free_list链表
// 已使用的物理页包括如下几部分:
// 1)第一个物理页是IDT所在,需要标识为已用
// 2)[IOPHYSMEM, EXTPHYSMEM)称为IO hole的区域,需要标识为已用。
// 3)EXTPHYSMEM是内核加载的起始位置,终止位置可以由boot_alloc(0)给出(理由是boot_alloc()分配的内存是内核的最尾部),这块区域也要标识
size_t i;
size_t io_hole_start_page = (size_t)IOPHYSMEM / PGSIZE;
size_t kernel_end_page = PADDR(boot_alloc(0)) / PGSIZE; //这里调了半天,boot_alloc返回的是虚拟地址,需要转为物理地址
for (i = 0; i < npages; i++) {
if (i == 0) {
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
} else if (i >= io_hole_start_page && i < kernel_end_page) {
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
} else {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
}

这个函数的主要作用是初始化之前分配的pages数组,并且构建一个PageInfo链表,保存空闲的物理页,表头是全局变量page_free_list。具体实现看注释。

接着实现page_alloc():

// Allocates a physical page.  If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes. Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
//
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
//
// Returns NULL if out of free memory.
//
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
struct PageInfo *ret = page_free_list;
if (ret == NULL) {
cprintf("page_alloc: out of free memory\n");
return NULL;
}
page_free_list = ret->pp_link;
ret->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO) {
memset(page2kva(ret), 0, PGSIZE);
}
return ret;
}

该函数的作用是:从page_free_list指向的链表中取一个PageInfo结构返回,根据参数alloc_flags决定要不要将这块内存初始化为0。需要注意的是,不需要增加PageInfo的pp_ref字段。

和page_alloc()对称的是page_free()实现如下:

// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
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 != 0 || pp->pp_link != NULL) {
panic("page_free: pp->pp_ref is nonzero or pp->pp_link is not NULL\n");
}
pp->pp_link = page_free_list;
page_free_list = pp;
}

该函数重新将参数pp对应的物理页设置为空闲状态。

重新回到mem_init()的流程中来,在调用page_init()后,会调用check_page_free_list(1)和check_page_alloc()。这两个函数通过一系列断言,判断我们的实现是否符合预期。需要注意的是check_page_free_list()中的这段代码:

	if (only_low_memory) {
// Move pages with lower addresses first in the free
// list, since entry_pgdir does not map all pages.
struct PageInfo *pp1, *pp2;
struct PageInfo **tp[2] = { &pp1, &pp2 };
for (pp = page_free_list; pp; pp = pp->pp_link) {
int pagetype = PDX(page2pa(pp)) >= pdx_limit;
*tp[pagetype] = pp;
tp[pagetype] = &pp->pp_link;
} //执行该for循环后,pp1指向(0~4M)中地址最大的那个页的PageInfo结构。pp2指向所有页中地址最大的那个PageInfo结构
*tp[1] = 0;
*tp[0] = pp2;
page_free_list = pp1;
}

刚开始也没看明白,最后在纸上涂涂画画了半天才搞明白,关键是要理解for循环结束后pp1和pp2所指向的地址的具体含义。这段代码的作用就是调整page_free_list链表的顺序,将代表低地址的PageInfo结构放到链表的表头处,这样的话,每次分配物理地址时都是从低地址开始。

这样第一部分就结束了,现在pages数组保存这所有物理页的信息,page_free_list链表记录这所有空闲的物理页。可以用page_alloc()和page_free()进行分配和回收。

执行完mem_init()后的物理内存如下:

Part 2: Virtual Memory

这部分主要的目的是实现一些函数操作页目录和页表从而达到实现虚拟地址到物理地址映射的目的。


Selector +--------------+ +-----------+
---------->| | | |
| Segmentation | | Paging |
Software | |-------->| |----------> RAM
Offset | Mechanism | | Mechanism |
---------->| | | |
+--------------+ +-----------+
Virtual Linear Physical

这张图展示了x86体系中虚拟地址,线性地址,物理地址的转换过程。在boot/boot.S中我们设置了全局描述符表(GDT),设置所有段的基地址都是0x0,所有虚拟地址的offset和线性地址都是相等的。

在lab1中已经安装了一个简易的页目录和页表,将虚拟地址[0, 4MB)映射到物理地址[0, 4MB),[0xF0000000, 0xF0000000+4MB)映射到[0, 4MB)。具体实现在kern/entry.S中,临时的页目录线性地址为entry_pgdir,定义在kern/entrypgdir.c中。

线性地址到物理地址的转换过程可以用下图表示:



通过这张图我们知道页目录和和页表实际上构成了一棵树结构,任何映射关系我们都只需要修改这棵树结构就能实现。

页表条目结构如下:



每一位的具体含义可以参考Intel 80386 Reference Manual

JOS内核有时候在仅知道物理地址的情况下,想要访问该物理地址,但是没有办法绕过MMU的线性地址转换机制,所以没有办法用物理地址直接访问。JOS将虚拟地址0xf0000000映射到物理地址0x0处的一个原因就是希望能有一个简便的方式实现物理地址和线性地址的转换。在知道物理地址pa的情况下可以加0xf0000000得到对应的线性地址,可以用KADDR(pa)宏实现。在知道线性地址va的情况下减0xf0000000可以得到物理地址,可以用宏PADDR(va)实现。

Exercise 4

该实验要求我们实现:

  1. pgdir_walk()
  2. boot_map_region()
  3. page_lookup()
  4. page_remove()
  5. page_insert()

pagedir_walk():

参数:

  1. pgdir:页目录虚拟地址
  2. va:虚拟地址
  3. create:布尔值

返回值:页表条目的地址

作用:给定pgdir,指向一个页目录,该函数返回一个指针指向虚拟地址va对应的页表条目(PTE)。

// 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.
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, then pgdir_walk returns NULL.
// Otherwise, pgdir_walk allocates a new page table page with page_alloc.
// - If the allocation fails, pgdir_walk returns NULL.
// - Otherwise, the new page's reference count is incremented,
// 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.
//
// 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.
//
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
// Fill this function in
pde_t* pde_ptr = pgdir + PDX(va);
if (!(*pde_ptr & PTE_P)) { //页表还没有分配
if (create) {
//分配一个页作为页表
struct PageInfo *pp = page_alloc(1);
if (pp == NULL) {
return NULL;
}
pp->pp_ref++;
*pde_ptr = (page2pa(pp)) | PTE_P | PTE_U | PTE_W; //更新页目录项
} else {
return NULL;
}
} return (pte_t *)KADDR(PTE_ADDR(*pde_ptr)) + PTX(va); //这里记得转为pte_t*类型,因为KADDR返回的的是void*类型。调了一个多小时才发现
}

结合图二的页转换的过程很容易理解,需要注意一点,最后返回的的时候KADDR(PTE_ADDR(pde_ptr))返回的void **类型如果直接加上PTX(va)是不对的,应该先转为(pte_t),再做加法运算,这个bug调了一个多小时才发现( ╯□╰ )。

boot_map_region()

参数:

  1. pgdir:页目录指针
  2. va:虚拟地址
  3. size:大小
  4. pa:物理地址
  5. perm:权限

作用:通过修改pgdir指向的树,将[va, va+size)对应的虚拟地址空间映射到物理地址空间[pa, pa+size)。va和pa都是页对齐的。

// 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
size_t pgs = size / PGSIZE;
if (size % PGSIZE != 0) {
pgs++;
} //计算总共有多少页
for (int i = 0; i < pgs; i++) {
pte_t *pte = pgdir_walk(pgdir, (void *)va, 1);//获取va对应的PTE的地址
if (pte == NULL) {
panic("boot_map_region(): out of memory\n");
}
*pte = pa | PTE_P | perm; //修改va对应的PTE的值
pa += PGSIZE; //更新pa和va,进行下一轮循环
va += PGSIZE;
}
}

思路很简单,看注释即可。

page_insert()

参数:

  1. pgdir:页目录指针
  2. pp:PageInfo结构指针,代表一个物理页
  3. va:线性地址
  4. perm:权限

返回值:0代表成功,-E_NO_MEM代表物理空间不足。

作用:修改pgdir对应的树结构,使va映射到pp对应的物理页处。

// 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 *pte = pgdir_walk(pgdir, va, 1); //拿到va对应的PTE地址,如果va对应的页表还没有分配,则分配一个物理页作为页表
if (pte == NULL) {
return -E_NO_MEM;
}
pp->pp_ref++; //引用加1
if ((*pte) & PTE_P) { //当前虚拟地址va已经被映射过,需要先释放
page_remove(pgdir, va); //这个函数目前还没实现
}
physaddr_t pa = page2pa(pp); //将PageInfo结构转换为对应物理页的首地址
*pte = pa | perm | PTE_P; //修改PTE
pgdir[PDX(va)] |= perm; return 0;
}

page_lookup()

参数:

  1. pgdir:页目录地址
  2. va:虚拟地址
  3. pte_store:一个指针类型,指向pte_t *类型的变量

返回值:PageInfo*

作用:通过查找pgdir指向的树结构,返回va对应的PTE所指向的物理地址对应的PageInfo结构地址。

// 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
struct PageInfo *pp;
pte_t *pte = pgdir_walk(pgdir, va, 0); //如果对应的页表不存在,不进行创建
if (pte == NULL) {
return NULL;
}
if (!(*pte) & PTE_P) {
return NULL;
}
physaddr_t pa = PTE_ADDR(*pte); //va对应的物理
pp = pa2page(pa); //物理地址对应的PageInfo结构地址
if (pte_store != NULL) {
*pte_store = pte;
}
return pp;
}

如果将page_insert()函数看作是“增”,那么page_lookup()就是“查”。查找一个虚拟地址va,对应的物理地址信息。

page_remve()

参数:

  1. pgdir:页目录地址
  2. va:虚拟地址

作用:修改pgdir指向的树结构,解除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 *pte_store;
struct PageInfo *pp = page_lookup(pgdir, va, &pte_store); //获取va对应的PTE的地址以及pp结构
if (pp == NULL) { //va可能还没有映射,那就什么都不用做
return;
}
page_decref(pp); //将pp->pp_ref减1,如果pp->pp_ref为0,需要释放该PageInfo结构(将其放入page_free_list链表中)
*pte_store = 0; //将PTE清空
tlb_invalidate(pgdir, va); //失效化TLB缓存
}

如果将page_insert()函数看作是“增”,page_lookup()是“查”,那么page_remove()就是“删”,删除线性地址va的映射关系,删除过后不可使用该虚拟地址,否则会出现页错误,lab3将处理该错误。

至此如果一切顺利,将通过mem_init()中check_page()的所有assert。

Part 3: Kernel Address Space

JOS将线性地址空间分为两部分,由定义在inc/memlayout.h中的ULIM分割。ULIM以上的部分用户没有权限访问,内核有读写权限。

/*
* Virtual memory map: Permissions
* kernel/user
*
* 4 Gig --------> +------------------------------+
* | | RW/--
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* : . :
* : . :
* : . :
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
* | | RW/--
* | Remapped Physical Memory | RW/--
* | | RW/--
* KERNBASE, ----> +------------------------------+ 0xf0000000 --+
* KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| |
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* | CPU1's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| PTSIZE
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* : . : |
* : . : |
* MMIOLIM ------> +------------------------------+ 0xefc00000 --+
* | Memory-mapped I/O | RW/-- PTSIZE
* ULIM, MMIOBASE --> +------------------------------+ 0xef800000
* | Cur. Page Table (User R-) | R-/R- PTSIZE
* UVPT ----> +------------------------------+ 0xef400000
* | RO PAGES | R-/R- PTSIZE
* UPAGES ----> +------------------------------+ 0xef000000
* | RO ENVS | R-/R- PTSIZE
* UTOP,UENVS ------> +------------------------------+ 0xeec00000
* UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebff000
* | Empty Memory (*) | --/-- PGSIZE
* USTACKTOP ---> +------------------------------+ 0xeebfe000
* | Normal User Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebfd000
* | |
* | |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* . .
* . .
* . .
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
* | Program Data & Heap |
* UTEXT --------> +------------------------------+ 0x00800000
* PFTEMP -------> | Empty Memory (*) | PTSIZE
* | |
* UTEMP --------> +------------------------------+ 0x00400000 --+
* | Empty Memory (*) | |
* | - - - - - - - - - - - - - - -| |
* | User STAB Data (optional) | PTSIZE
* USTABDATA ----> +------------------------------+ 0x00200000 |
* | Empty Memory (*) | |
* 0 ------------> +------------------------------+ --+
*
* (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
* "Empty Memory" is normally unmapped, but user programs may map pages
* there if desired. JOS user programs map pages temporarily at UTEMP.
*/

Exercise 5

该实验需要我们填充mem_init()中缺失的代码,使用part2的增删改函数初始化内核线性地址空间。

在mem_init()函数的check_page();后添加如下语句:

	//////////////////////////////////////////////////////////////////////
// Map 'pages' read-only by the user at linear address UPAGES
// Permissions:
// - the new image at UPAGES -- kernel R, user R
// (ie. perm = PTE_U | PTE_P)
// - pages itself -- kernel RW, user NONE
// Your code goes here:
// 将虚拟地址的UPAGES映射到物理地址pages数组开始的位置
boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);
//////////////////////////////////////////////////////////////////////
// Use the physical memory that 'bootstack' refers to as the kernel
// stack. The kernel stack grows down from virtual address KSTACKTOP.
// We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
// to be the kernel stack, but break this into two pieces:
// * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
// * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
// the kernel overflows its stack, it will fault rather than
// overwrite memory. Known as a "guard page".
// Permissions: kernel RW, user NONE
// Your code goes here:
// 'bootstack'定义在/kernel/entry.
boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W); //////////////////////////////////////////////////////////////////////
// Map all of physical memory at KERNBASE.
// Ie. the VA range [KERNBASE, 2^32) should map to
// the PA range [0, 2^32 - KERNBASE)
// We might not have 2^32 - KERNBASE bytes of physical memory, but
// we just set up the mapping anyway.
// Permissions: kernel RW, user NONE
// Your code goes here:
boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);

执行完mem_init()后kern_pgdir指向的内核页目录代表的虚拟地址空间到物理地址空间映射可以用下图来表示:



如何仔细看图和上面的代码,会觉得奇怪,UVPT开始的这一页是什么时候映射的?实际上早在mem_init()开始的时候就有这么一句kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;,页目录表的低PDX(UVPT)项指向页目录本身,也就是说虚拟地址UVPT开始处的0x400000字节映射到物理地址PADDR(kern_pgdir)处。

总结

至此,lab2的所有实验都已完成。如果顺利运行./grade-lab2会看到:



该实验大体上做三件事:

  1. 提供管理物理内存的数据结构和函数,可总结为下图:
  2. 提供修改页目录和页表树结构的函数,从而达到虚拟页到物理页映射的目的。可总结为下图:
  3. 用前面两部分的函数建立内核的线性地址空间。内核的线性地址空间到物理内存的映射可总结为下图:

    现在我们可以直接使用UPAGES这个虚拟地址直接访问pages数组,使用UVPT访问内核页目录表,使用KSTACKTOP访问内核栈。

具体代码在:https://github.com/gatsbyd/mit_6.828_jos

如有错误,欢迎指正:

15313676365

MIT-6.828-JOS-lab2:Memory management的更多相关文章

  1. MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap

    Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:        ...

  2. MIT 6.828 | JOS | 关于虚拟空间和物理空间的总结

    Question: 做lab过程中越来越迷糊,为什么一会儿虚拟地址是4G 物理地址也是4G ,那这有什么作用呢? 解决途径: 停下来,根据当前lab的进展,再回头看上学期操作系统的ppt & ...

  3. MIT 6.828 JOS学习笔记15. Lab 2.1

    Lab 2: Memory Management lab2中多出来的几个文件: inc/memlayout.h kern/pmap.c kern/pmap.h kern/kclock.h kern/k ...

  4. MIT 6.828 JOS学习笔记0. 写在前面的话

    0. 简介 操作系统是计算机科学中十分重要的一门基础学科,是一名计算机专业毕业生必须要具备的基础知识.但是在学习这门课时,如果仅仅把目光停留在课本上一些关于操作系统概念上的叙述,并不能对操作系统有着深 ...

  5. MIT 6.828 JOS学习笔记18. Lab 3.2 Part B: Page Faults, Breakpoints Exceptions, and System Calls

    现在你的操作系统内核已经具备一定的异常处理能力了,在这部分实验中,我们将会进一步完善它,使它能够处理不同类型的中断/异常. Handling Page Fault 缺页中断是一个非常重要的中断,因为我 ...

  6. MIT 6.828 JOS学习笔记17. Lab 3.1 Part A User Environments

    Introduction 在这个实验中,我们将实现操作系统的一些基本功能,来实现用户环境下的进程的正常运行.你将会加强JOS内核的功能,为它增添一些重要的数据结构,用来记录用户进程环境的一些信息:创建 ...

  7. MIT 6.828 JOS学习笔记7. Lab 1 Part 2.2: The Boot Loader

    Lab 1 Part 2 The Boot Loader Loading the Kernel 我们现在可以进一步的讨论一下boot loader中的C语言的部分,即boot/main.c.但是在我们 ...

  8. MIT 6.828 JOS学习笔记16. Lab 2.2

    Part 3 Kernel Address Space JOS把32位线性地址虚拟空间划分成两个部分.其中用户环境(进程运行环境)通常占据低地址的那部分,叫用户地址空间.而操作系统内核总是占据高地址的 ...

  9. MIT 6.828 JOS学习笔记10. Lab 1 Part 3: The kernel

    Lab 1 Part 3: The kernel 现在我们将开始具体讨论一下JOS内核了.就像boot loader一样,内核开始的时候也是一些汇编语句,用于设置一些东西,来保证C语言的程序能够正确的 ...

  10. MIT 6.828 JOS学习笔记5. Exercise 1.3

    Lab 1 Exercise 3 设置一个断点在地址0x7c00处,这是boot sector被加载的位置.然后让程序继续运行直到这个断点.跟踪/boot/boot.S文件的每一条指令,同时使用boo ...

随机推荐

  1. bzoj2616: SPOJ PERIODNI——笛卡尔树+DP

    不连续的处理很麻烦 导致序列DP又找不到优秀的子问题 自底向上考虑? 建立小根堆笛卡尔树 每个点的意义是:高度是(自己-father)的横着的极大矩形 子问题具有递归的优秀性质 f[i][j]i为根子 ...

  2. Hadoop生态圈-Kafka的本地模式部署

    Hadoop生态圈-Kafka的本地模式部署 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Kafka简介 1>.什么是JMS 答:在Java中有一个角消息系统的东西,我 ...

  3. POJ-1459 Power Network(最大流)

    https://vjudge.net/problem/POJ-1459 题解转载自:優YoU http://user.qzone.qq.com/289065406/blog/1299339754 解题 ...

  4. bzoj千题计划239:bzoj4069: [Apio2015]巴厘岛的雕塑

    http://www.lydsy.com/JudgeOnline/problem.php?id=4069 a!=1: 从高位到低位一位一位的算 记录下哪些位必须为0 dp[i][j] 表示前i个数分为 ...

  5. CS229 笔记02

    CS229 笔记02 公式推导 $ {\text {For simplicity, Let }} A, B, C \in {\Bbb {R}}^{n \times n}. $ ​ $ {\bf {\t ...

  6. MySQL忘记密码了怎么办?

    接手一个项目时,如果上一位负责人没有把项目文档.账号密码整理好是一件很头疼的事情.. 例如,当你想打开MySQL数据库的时候 输入: mysql -u root -p 一回车想输入密码,发现密码错误! ...

  7. 第5月第27天 cocos2d

    1. 流程是这样的: 在CCApplication的run函数中,显示设备链调用相应的场景显示函数drawScene来绘制场景,然后调用了CCScheduler的update函数,在这个函数里,对所有 ...

  8. DataTable转Json(兼容easyUI特殊json分页)

    用法:上述方法是DataTable的扩展方法:静态类静态方法,变量前用this (一)ps:普通datatable转标准json DataTable dt = 获取db中的datatable数据. s ...

  9. Linux awk工具简单学习记录

    awk是一个文本分析工具,它把文件逐行读入,以特定符号将每行切分(默认空格为分隔符),切开的部分再进行各种分析处理. awk其名称得自于它的创始人Alfred Aho .Peter Weinberge ...

  10. [Alg] 尺取法

    尺取法是在线性结构中进行搜寻满足某一条件的区间的方法. 该方法保存两个索引--首索引begin.尾索引end.判断 [begin, end] 区间是否满足条件. 移动 [begin, end] 区间的 ...