MIT6.828 Lab2 內存管理


Lab2

0. 任務介紹

你將編寫一個內存管理代碼。主要分為兩大部分。分別對物理內存和虛擬內存的管理。

  • 對於物理內存,每次分配內存分配器會為你分配4096bytes。也稱為一個頁(在大部分操作系統中一個頁的大小都是4B)你需要維護一個數據結構來記錄哪個物理頁是空閑的哪個物理頁是已被占用的。以及有多少進程共享已分配的頁。並且你需要編寫程序來進行內存的分配和回收
  • 對於虛擬內存,它將內核和用戶軟件使用的虛擬地址映射到物理內存中的地址。 x86硬件的內存管理單元(MMU)在指令使用內存時執行映射,查閱一組頁面表。 您將根據我們提供的規范設置MMU的頁面表。

1. Part1: Physical Page Management

操作系統必須跟蹤哪部分物理內存是被使用的以及哪部分物理內存是空閑的。你需要在切入到虛擬內存之前完成這一操作,因為當使用虛擬內存的時候我們需要頁表來進行管理,而你需要為頁表分配物理內存。

下面的圖來自於[https://blog.csdn.net/qq_40871466/article/details/103922416]

img

你需要實現 kern/pmap.c的下列函數

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

check_page_free_list() and check_page_alloc() test your physical page allocator. You should boot JOS and see whether check_page_alloc() reports success. Fix your code so that it passes.

1.1 實現boot_alloc

在lab1我們知道了pc的啟動過程。這里是在內核執行的init.c中先調用了mem_init

根據我的調試輸出(printf)我發現.bss的地址是 0xf01156a0這個地址在虛擬地址0xf0000000之上。我們知道內核的虛擬地址是在0xf0000000為起點的。隨后是內核的代碼段+數據段然后就是.bss所以這里bss大於內核虛擬地址起始位置是合理的.

boot_alloc就是在.bss之上分配制定大小為n的區域。注意這里都是在虛擬內存地址下進行的操作

  1. 如果n為0則直接范圍nextfree的地址。也就是把.bss向上取整(為了都符合一頁一頁的存儲形式。分頁管理)
  2. 如果不為0則為他分配內存。其實就是把nextfree的地址往后移動 (n * PGSIZE)。不過我們要返回這段地址的起始地址。就相當於一個page數組的起始地址
static void *
boot_alloc(uint32_t n)
{
	static char *nextfree;	// virtual address of next byte of free memory
	char *result;

	if (!nextfree) {
		extern char end[];
		cprintf("end is %08x\n",end);
		nextfree = ROUNDUP((char *) end, PGSIZE);
	}
	cprintf("nextfree is %08x\n",nextfree);
	// 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.
	if (n == 0) {
		return nextfree;
	} 
	//allocate
	result = nextfree;
	nextfree = ROUNDUP((char *)(nextfree + n), PGSIZE);
	return result;
}

1.2 實現mem_init

在mem_init中我們會調用兩次boot_alloc。第一次為了創建頁目錄。第二次則為了創建所有的物理頁表。分別為它們分配內存然后memset成0。

	// create initial page directory.
	kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
	memset(kern_pgdir, 0, PGSIZE);
	//////////////////////////////////////////////////////////////////////
	// Recursively insert PD in itself as a page table, to form
	// a virtual page table at virtual address UVPT.
	// (For now, you don't have understand the greater purpose of the
	// following line.)

	// Permissions: kernel R, user R
	kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
	cprintf("Page nubmer %d\n",npages);
	//////////////////////////////////////////////////////////////////////
	// 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.
	// Your code goes here:
	//	all 32768 number pages
	pages = (struct PageInfo *) boot_alloc(sizeof(struct PageInfo) * npages);
	memset(pages, 0, sizeof(struct PageInfo) * npages);

1.3 實現page_alloc

這里給了我們示例代碼。不過這里把所有的pages都初始化了成了0和可用。這顯然是不合理的

  1. 根據下圖和實驗中給的提示。base_memory(也就是 1mb + extended_memoy)這一段 。的low memory是可以被分配成use的

    但是注意最下面的一個page不可以(實驗中有提到保存實模式的一些信息)

    ![](/Users/zhouxiaolun/Library/Application Support/typora-user-images/image-20210623224201539.png)

  2. 第二就是extended memory里會存有內核的信息。我們要找到內核的結束位置,然后給剩余部分進行初始化

    這里就可以簡單利用boot_alloc(0)。因為這個會返回內核的結束位置對應的虛擬地址。

    但是我們要找的是這個虛擬地址定於的物理地址在哪個page中。也就是要找到它在pages數組中的標號。

    這里實驗給我們提供了一個宏定義page2kva即可獲得它對應的物理地址。然后除頁表大小就可以獲得對應的標號

    好了代碼已經呼之欲出了

    	cprintf("npages_basemem is %d\n",npages_basemem);
    	cprintf("pages addr is %0x8 \n", pages);
    	size_t i;
    	//all low memeory is free expect 0 page
    	for (i = 1; i < npages_basemem; i++) {
    		cprintf("pages addr is %0x8 \n", pages[i]);
    		pages[i].pp_ref = 0;
    		pages[i].pp_link = page_free_list;
    		page_free_list = &pages[i];
    	}
    	i = PADDR(boot_alloc(0)) / PGSIZE;
    	for (; i < npages; i++) {
    		pages[i].pp_ref = 0;
    		pages[i].pp_link = page_free_list;
    		page_free_list = &pages[i];
    	}
    }
    

    1.4 實現page_free

    這個就比較簡單了。只需要簡單的把要釋放的頁加入到free_page_list中就好。

    根據實驗中給出的提示,可以很容易的寫出來

    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("no shoule page free");
    	}
    	//head insert 
    	pp -> pp_link = page_free_list;
    	page_free_list = pp;
    }
    

2. Part2: Virtual Memory

跳過中間的一些廢話,直接開始exercise4。這里要求我們編寫代碼來管理頁面表。要插入和刪除linear-to-physical(其實就是虛擬地址和物理地址之間的mappings,並按需分配頁。

這個是JOS所用的32位虛擬地址的分布

img

下面就是虛擬地址的翻譯過程。這個學過os的應該非常熟悉了吧。這里的page_dirpage_table其實就是一個二級頁表。的多級索引非常簡單。

整體過程就是我們先通過CR3寄存器找到PAGE_DIR所在的位置,然后通過虛擬地址的前10位在PAGE_DIR中獲取到下一級頁表,也就是PAGE_TABLE的地址。隨后通過虛擬地址的12-21這10位去找到對應的PAGE_FRAME的地址。從里面獲取到ppa的地址結合OFFSET就可以得到最終的物理地址了。

img

好了搞清楚大概邏輯之后,下面開始完成第二部分的代碼

2.1 pgdir_walk

這個代碼是后面四個代碼的基礎,因此一定要小心認真,這里的意思就是說給你pgdir的地址。和虛擬地址va你要返回一個指向pte的指針。pte就是頁表條目在最后一層頁表對應位置處。

  1. 首先我們通過幾個宏定義把虛擬地址分解
  2. 如果page_dir對應的PTE_P也就是有效位為0的話則表明相關的頁表條目並不存在
  3. 如果不存在的話需要通過create標記來判斷是否需要創建對應的頁
  4. 這里根據提示我們需要把新創建頁的物理地址存儲在對應的page_dir位置處
  5. 然后就是在page_table中找到對應的PTE返回
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
	// Fill this function in
	uintptr_t dir = PDX(va); //表示對應的page_dir索引
	uintptr_t page = PTX(va); // 表示對應的page_table索引
	uintptr_t offset = PGOFF(va); // 表示在page中對應的頁內偏移
	pde_t dir_entry = pgdir[dir]; // 首先要判斷這個虛擬地址是否有映射
	if (!(dir_entry & PTE_P)) {
		if (create) {
			// allocate
			struct PageInfo *newPage = page_alloc(ALLOC_ZERO);
            if (newPage == NULL) {
                // allocation failed
                return NULL;
            }
			newPage->pp_ref++;
			pgdir[dir]  = (pde_t)page2pa(newPage)|PTE_P|PTE_U|PTE_W;
		} else {
			return NULL;
		}
	}
	//
	pte_t *ptab = (pte_t *)KADDR(PTE_ADDR(pgdir[dir]));
	return &ptab[page];
}

2.2 boot_map_region

這個函數的實現就比較簡單了。要求是把虛擬地址[va, va+size)映射到物理地址[pa, pa+size],使用權限位為perm|PTE_P,只是在UTOP上方做靜態映射,所以不能修改pp_ref字段.

基本上就是通過上面實現的pgdir_walk函數找到給定虛擬地址對應的pte。然后修改pte條目即可

static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
	// Fill this function in
	uintptr_t start = 0;
	for ( ; start < size; start += PGSIZE, va += PGSIZE, pa += PGSIZE) {
		pte_t *pte = pgdir_walk(pgdir,(void *) va, 1);
		*pte = pa | perm | PTE_P;
	}
}

2.3 Page_lookup

返回一個虛擬地址va映射的頁面。如果pte_store不是0,那么將對應的頁表項地址存到pte_store的地址里(用於結果返回)。如果沒有頁面映射在va那么返回NULL。提示:使用pgdir_walk和pa2page

struct PageInfo *
pgee_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{
	// Fill this function in
	pte_t *pte = pgdir_walk(pgdir,(void *) va, 0);
	if (!pte) {
		return NULL;
	}
	if (*pte && !(*pte & PTE_P)) {
		return NULL;
	}
	if (pte_store) {
		*pte_store = pte;
	}
	struct PageInfo* page = pa2page(PTE_ADDR(*pte));
	return page;
}

基本上通過給的提示就可以實現這個函數

2.4 Page_remove

也是通過提示。移除給定的va對應的映射。

  1. 如果給定的va在頁表中沒有對應映射則直接返回。
  2. 否則把對應的pte清0,然后調用tlb_invalidatepage_decref
void
page_remove(pde_t *pgdir, void *va)
{
	// Fill this function in
	pte_t * pte;
	struct PageInfo *page = page_lookup(pgdir,va,&pte);
	if (!page) {
		return;
	}
	*pte = 0;
	tlb_invalidate(pgdir,va);
	page_decref(page);
}

2.5 Page_insert

把物理頁pp映射在虛擬地址va,頁表項權限設置為perm|PTE_P。

  1. 如果已經有一個頁面在va,它應該先調用page_remove()刪除
  2. 如有必要,應按需分配頁表並將其插入“ pgdir”。插入成功pp->ref應該自增。如果以前有頁面位於“ va”,則TLB必須無效。
  3. 提示:使用pgdir_walk,page_remove和page2pa。
  4. 根據提示我們需要分配頁表並將其插入pgdir。這不是就是前面實現的pgir_walk把crate設置成1的功能。
  5. 同樣如果以前有頁面位於va。則讓他的tlb無效。。這里聽起來很麻煩,但實際上只需要調用page_remove原來va對應的映射移除即可。而且page_remove已經實現了讓tlb無效的操作。
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
	// Fill this function in
	pte_t *pte = pgdir_walk(pgdir,(void *) va, 1);
	if (!pte) {
		return -E_NO_MEM;
	}
	pp->pp_ref++;
	if (*pte & PTE_P) {
		page_remove(pgdir,va);
	}
	*pte = page2pa(pp) | perm | PTE_P;
	return 0;
}

3. Part3 : Kernel Address Space

第三部分需要我們補齊mem_init函數

只要跟隨提示來完成對於內核部分的一些映射。按照下面這樣做就好了

boot_map_region(kern_pgdir, UPAGES, PTSIZE, 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);

3.1 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:

    Entry Base Virtual Address Points to (logically):
    1023 0xff000000 Page table for top 4MB of phys memory
    1022 ? ?
    959 0xefc00000 cpu0's kernel stack(0xefff8000),cpu1's kernel stack(0xeffe8000)
    956 0xef000000 npages of PageInfo(0xef000000)
    952 0xee000000 bootstack
    2 0x00800000 Program Data & Heap
    1 0x00400000 Empty
    0 0x00000000 [see next question]

    這個地方要參考一下memlayout.h就可以寫出了

    其實主要搞清楚幾個重要的就可以了

    比如0xef000000表示UPAGES

    oxefc00000表示內核棧等等

  2. 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?

    通過把頁表項中的 Supervisor/User位置0,那么用戶態的代碼就不能訪問內存中的這個頁。

  3. What is the maximum amount of physical memory that this operating system can support? Why?

    這個操作系統利用一個大小為4MB的空間也就是UPAGES這一段。來存放所有的頁的PageInfo結構體信息,每個結構體的大小為8B,所以一共可以存放512K個PageInfo結構體,所以一共可以出現512K個物理頁,每個物理頁大小為4KB,自然總的物理內存占2GB。

  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?

    這個問題是說如果我們現在的物理頁達到最大,那么管理這些內存所需要的額外空間開銷有多少

    首先我們所有的pageinfo需要4mb。然后需要存放頁目錄表。一共1024個每一個需要4B所以一共4kb

    還有存放當前的頁表。頁表是1024 * 4kb = 4mb

    所以一共需要4MB + 4MB + 4KB = 8MB + 4KB

  5. 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?

    1. 是通過下面的代碼來跳轉到kernbase之上的虛擬地址的

    mov $relocated, %eax

    jmp *%eax

    1. 是因為我們把[0,4mb]和[KernalBASE,KERNALBASR + 4MB]這段的虛擬地址都映射到了0-4MB的物理地址上,因此無論EIP在高位還是在低位都可以運行。必須這樣做的原因是,如果只映射高位地址。在我們剛開啟分頁模式之后就會crash。

      因為剛開始我們訪問的還是地位地址。是通過jump來跳轉到高位》

4. Part4 : Challenge

//TODO


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM