建議先看完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 結構體。
- 這個題上個實驗解釋的很清楚了