建议先看完xv6book的Chapter1和Chapter2
Introduction
Memory management有两部分:physical memory allocator for the kernel和virtual memory。
The x86 hardware's memory management unit (MMU) performs the mapping when instructions use memory, consulting a set of page tables.
Getting started
git pull
git checkout -b lab2 origin/lab2
git merge lab1
# 如果有冲突就处理一下
# git add .
# git commit
memlayout.h
描述了virtual address space的布局,你需要通过修改pmap.c
来实现virtual address space。你会使用PageInfo
结构体来跟踪空闲的和已经被分配的physical memory page。kclock.c
和kclock.h
控制电池供电时钟和CMOS RAM硬件。BIOS记录了PC包含的物理内存量。pmap.c
的代码为了弄清有多少物理内存需要读取硬件设备。这个实验需要理解memlayout.h
pmap.h
inc/mmu.h
中的定义。
Part 1: Physical Page Management
进入kernel后,在i386_init
函数中调用mem_init
函数,初始化kernel的内存。
Execrise 1
补全以下函数:
boot_alloc()
mem_init() (到check_page_free_list(1);处为止)
page_init()
page_alloc()
page_free()
check_page_free_list() 和 check_page_alloc() 会测试你的 physical page allocator.
boot_alloc()
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // 下一个空闲内存的首字节虚拟地址
char *result;
// end是linker自动生成的,指向bss段的尾部
// 使用objdump -h obj/kern/kernel查看一下各段的地址
// 可知.bss就是kernel文件末尾的一个段
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// LAB 2: Your code here.
if (n == 0) {
return nextfree;
}
result = nextfree;
nextfree += ROUNDUP(n, PGSIZE);
return result;
}
mem_init()
void
mem_init(void)
{
uint32_t cr0;
size_t n;
// 查明这个机器有多少内存
i386_detect_memory();
//panic("mem_init: This function is not finished\n");注释掉这个
// 创建初始页目录,并初始化为0
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
// Permissions: kernel R, user R. 目前不需要理解这段
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
// 分配一个PageInfo数组,共npages个PageInfo,并初始化为0
pages = (struct PageInfo*)boot_alloc(npages * sizeof(struct PageInfo));
memset(pages, 0, npages * sizeof(struct PageInfo));
---省略---
page_init()
// 初始化 page structure 和 内存free表
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?
size_t i;
// 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...)
pages[0].pp_ref = 1; // 代表已被使用
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
for (i = 1; i < npages_basemem; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
for (i = (IOPHYSMEM >> PGSHIFT); i < (EXTPHYSMEM >> PGSHIFT); i++) {
pages[i].pp_ref = 1;
}
// 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?
//
size_t first_free_page = (PADDR(boot_alloc(0))) >> PGSHIFT;
for (i = (EXTPHYSMEM >> PGSHIFT); i < first_free_page; i++) {
pages[i].pp_ref = 1;
}
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
for (i = first_free_page; i < npages; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
// page_free_list指向表头
page_free_list = &pages[i];
}
}
page_alloc()
struct PageInfo* page_alloc(int alloc_flags) {
if (page_free_list == NULL) {
return NULL;
}
struct PageInfo* result = page_free_list;
page_free_list = result->pp_link;
result->pp_link = NULL;
if (alloc_flags && ALLOC_ZERO) {
memset(page2kva(result), 0, PGSIZE);
}
// Fill this function in
return result;
}
page_free()
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 failed. pp->pp_ref is nonzero or pp->pp_link is not NULL!");
return;
}
pp->pp_link = page_free_list;
page_free_list = pp;
}
然后运行JOS,输出一下内容则说明你写对了:
check_page_free_list() succeeded!
check_page_alloc() succeeded!
Part 2: Virtual Memory
先了解 x86's protected-mode memory management architecture: namely segmentation and page translation、逻辑地址、线性地址、物理地址
请看嘤特尔的 Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B, 3C, and 3D: System Programming Guide 里面的第三章
分段和分页的过程见Figure 3-1. Segmentation and Paging
Exercise 3
make qemu
后,在终端里Ctrl-a c
进入 QEMU monitor。使用xp
和x
命令可以查看物理地址和虚拟地址的内存内容,info tlb
可以查看page tables的信息(我的版本是4.2.0,如果用的是mit的patched版本,则是info pg
),info mem
可以查看哪个范围的虚拟地址被map。
Reference counting
一些物理页面同时被多个虚拟页面映射,struct PageInfo 的 pp_ref 就是引用计数,当这个数值为0时,就可以将其加入free list
Exercise 4
实现以下函数:
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()
实现可以参考xv6,嘤特尔开发人员手册卷三 Figure 4-2 Figure 4-4 Table 4-5
pgdir_walk()
pte_t* pgdir_walk(pde_t* pgdir, const void* va, int create) {
// pde 即 page directory entry
pde_t* pde = &pgdir[PDX(va)];
pte_t* pgtab; // 指向pg table的指针
if (*pde & PTE_P) {
// 先获取page table的地址
pgtab = KADDR(PTE_ADDR(*pde));
} else { // 如果相关page table不存在,就考虑alloc一个pgtab
// 如果不创建那就直接返回NULL
if (!create) {
return NULL;
}
// 创建时将页面内容置0
struct PageInfo* pp = page_alloc(ALLOC_ZERO);
if (pp == NULL) {
return NULL;
}
pp->pp_ref++;
pgtab = (pte_t*)page2kva(pp);
*pde = PADDR(pgtab) | PTE_P | PTE_W | PTE_U;
}
return &pgtab[PTX(va)];
}
boot_map_region()
static void boot_map_region(pde_t* pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm) {
pte_t* pte;
uintptr_t last = ROUNDDOWN(va + size - 1, PGSIZE);
va = ROUNDDOWN(va, PGSIZE);
pa = ROUNDDOWN(pa, PGSIZE);
for (;;) {
if ((pte = pgdir_walk(pgdir, (void*)va, 1)) == NULL) {
return;
}
*pte = pa | perm | PTE_P;
if (va == last) {
return;
}
va += PGSIZE;
pa += PGSIZE;
}
}
page_lookup()
struct PageInfo* page_lookup(pde_t* pgdir, void* va, pte_t** pte_store) {
pte_t* pte;
if ((pte = pgdir_walk(pgdir, va, 0)) == NULL) {
return NULL;
}
// pte_store 是指向一个 pte 的指针
if (pte_store) {
*pte_store = pte;
}
// Fill this function in
return pa2page(PTE_ADDR(*pte));
}
page_lookup()
void page_remove(pde_t* pgdir, void* va) {
pte_t* pte;
struct PageInfo* page = page_lookup(pgdir, va, &pte);
if (page == NULL) {
return;
}
page_decref(page);
tlb_invalidate(pgdir, va);
*pte = 0;
// Fill this function in
}
page_insert()
int page_insert(pde_t* pgdir, struct PageInfo* pp, void* va, int perm) {
pte_t* pte = pgdir_walk(pgdir, va, 1);
if (pte == NULL) {
return -E_NO_MEM; // 没有空间了
}
// 一定要在page_remove之前增加引用,否则可能被加入page_free_list
pp->pp_ref++;
if (*pte & PTE_P) { // 如果该页表项已经存在
// 否则移除另一个va的映射
page_remove(pgdir, va);
}
*pte = page2pa(pp) | perm | PTE_P;
// Fill this function in
return 0;
}
运行make qemu
,会输出:
check_page() succeeded!
Part 3: Kernel Address Space
Exercise 5
填写mem_init()
中调用check_page()
后的代码
boot_map_region(kern_pgdir, UPAGES, npages * sizeof(struct PageInfo), PADDR(pages), PTE_U);
boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);
boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);
最后make qemu
,会输出:
check_kern_pgdir() succeeded!
check_page_free_list() succeeded!
check_page_installed_pgdir() succeeded!
Question
- 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:
Entry | Base Virtual Address | Points to (logically): |
---|---|---|
1023 | 0xffc00000 | Page table for top 4MB of phys memory |
... | ... | ... |
960 | 0xf0000000 | Page table for [0,4)MB of phys memory |
959 | 0xefc00000 | Kernel Stack and Invalid Memory |
... | ... | ... |
957 | 0xef400000 | UVPT, User read-only virtual page table |
956 | 0xef000000 | UPAGES, Read-only copies of the Page structures |
... | ... | ... |
- 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?
Page-Directory Entry 和 Page-Table Entry 的U/S
位(User/supervisor),如果没有将其置1,那么用户将没有访问权限
- What is the maximum amount of physical memory that this operating system can support? Why?
2^32 bytes = 4 GB
- 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?
需要使用到 1 个 page directory 和 1024 个 page table,1024*1024 个 PageInfo 结构体。
- 这个题上个实验解释的很清楚了