練習0
填寫已有實驗
本實驗依賴實驗1.請把要做的實驗1的代碼填入本實驗中代碼有lab1的註釋相應部分
首先利用meld工具比較兩個文件的差異
發現缺失的是kdebug.c、trap.c
兩個文件的相關代碼,補全後進行下一練習
首先運行
報錯,看來就是需要進行實驗的所有編程才能完整的運行
練習1
實現firstfit連續物理內存分配算法(需要編程)
在實現firstfit內存分配算法的回首函數時,要考慮地址連續的空閒塊之間的合併操作
在實驗前先仔細學習一下firstfit算法
原理
要求空閒分區鏈以地址遞增的次序鏈接。在分配內存時,從鏈首開始順序查找,直至找到一個大小能滿足要求的分區爲止;然後再按照作業的大小,從該分取中劃出一塊內存空間分配給請求者,餘下的空閒分區仍留在空閒鏈中。若從鏈首直到鏈尾都不能找到一個能滿足要求的分區,則此次內存分配失敗,返回。該算法傾向於優先利用內存中低地址部分的空閒分區,從而保留了高址部分的大空閒區。這給爲以後到達的大作業分配大的內存空間創造了條件,其缺點是低址部分不斷被劃分,會留下許多難以利用的、很小的空閒分區,而每次查找又都是從低址部分開始,這無疑會增加查找可用空閒分區時的開銷。
大致流程圖
爲了與以後的分頁機制配合,首先需要建立對整個計算機的每一個物理頁的屬性,用結構體Page來表示,它包含了映射此物理頁的虛擬頁個數,描述物理頁屬性的flags和雙向鏈接各個Page結構的page_link雙向鏈表。
struct Page{
int ref;
uint32_t flags;
unsigned int property;
list_entry_t page_link;
}
ref
表示該頁被頁表的引用記數。如果這個頁被頁表引用了,即在某頁表中有一個頁表項設置一個虛擬頁到這個Page管理的物理頁的映射關係,就會把Page的ref加一。反之,若頁表項取消,即映射關係解除,就減一。
flags
表示此物理頁的狀態標記,有兩種屬性,bit 0表示是否被保留,如果被保留了則設爲1,且不能放到空閒頁鏈表中,即這樣的頁不是空閒頁,不能動態分配與釋放。比如內核代碼佔用的空間。bit 1表示此頁是否是空閒的。如果設置爲1,表示這頁是空閒的,可以被分配;如果設置爲0,表示這頁已經被分配出去了,不能被再二次分配。
property
用來記錄某連續內存空閒塊的大小(即地址連續的空閒頁的個數)。這裏需要注意的是用到此成員變量的這個Page比較特殊,是連續內存空閒地址最小的一夜(即第一頁)。
page_link
是便於把多個連續內存空閒塊鏈接在一起的雙向鏈表指針,連續內存空閒塊利用這個頁的成員變量page_link來鏈接比它地址小和大的其他連續內存空閒塊
爲了有效的管理這些小連續內存空閒塊,所有的連續內存空閒塊可用一個雙向鏈表來管理,便於分配和釋放,爲此定義一個free_area_t
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // number of free pages in this free list
} free_area_t;
free_list
是一個list_entry結構的雙向鏈表指針nr_free
則記錄當前空閒頁的個數
有了這兩個數據結構,就可以管理起來整個以頁尾單位的物理內存空間
理解完原理,開始進行實驗
首先根據實驗指導書,我們第一個實驗需要完成的主要是default_pmm.c
中的default_init
,default_init_memmap
,default_alloc_pages
, default_free_pages
幾個函數的修改。
一些標誌的定義:
default_init
static void
default_init(void) {
list_init(&free_list);
nr_free = 0;
}
看下注釋
(2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0.
free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks.
根據註釋代碼已經完成無需改動
default_init_memmap
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));
p->flags = p->property = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
nr_free += n;
list_add(&free_list, &(base->page_link));
}
查看一下注釋
* (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap
* This fun is used to init a free block (with parameter: addr_base, page_number).
* First you should init each page (in memlayout.h) in this free block, include:
* p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c),
* the bit PG_reserved is setted in p->flags)
* if this page is free and is not the first page of free block, p->property should be set to 0.
* if this page is free and is the first page of free block, p->property should be set to total num of block.
* p->ref should be 0, because now p is free and no reference.
* We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); )
* Finally, we should sum the number of free mem block: nr_free+=n
相關定義
根據註釋修改代碼
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));//判斷是否是保留頁,斷言手法
p->flags = p->property = 0;//設置標誌位
SetPageProperty(p);//設置bit
set_page_ref(p, 0);//清空被引用的次數
list_add(&free_list, &(p->page_link));//將此頁插入到空閒頁的鏈表裏面
}
base->property = n;//連續內存空閒塊的大小爲n
//SetPageProperty(base);
nr_free += n;//說明有n個連續空閒塊
//list_add(&free_list, &(base->page_link));
}
default_alloc_pages
此函數是用於爲進程分配空閒頁。其分配的步驟如下:
- 尋找足夠大的空閒塊,如果找到了,重新設置標誌位
- 從空閒鏈表中刪除此頁
- 判斷空閒塊大小是否合適 ,如果不合適,分割頁塊 ,如果合適則不進行操作
- 計算剩餘空閒頁個數
- 返回分配的頁塊地址
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
while ((le = list_next(le)) != &free_list) {
struct Page *p = le2page(le, page_link);
if (p->property >= n) {
page = p;
break;
}
}
if (page != NULL) {
list_del(&(page->page_link));
if (page->property > n) {
struct Page *p = page + n;
p->property = page->property - n;
list_add(&free_list, &(p->page_link));
}
nr_free -= n;
ClearPageProperty(page);
}
return page;
}
查看一下注釋
* (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr
* of malloced block.
* (4.1) So you should search freelist like this:
* list_entry_t le = &free_list;
* while((le=list_next(le)) != &free_list) {
* ....
* (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n?
* struct Page *p = le2page(le, page_link);
* if(p->property >= n){ ...
* (4.1.2) If we find this p, then it means we find a free block(block size >=n), and the first n pages can be malloced.
* Some flag bits of this page should be setted: PG_reserved =1, PG_property =0
* unlink the pages from free_list
* (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block,
* (such as: le2page(le,page_link))->property = p->property - n;)
* (4.1.3) re-caluclate nr_free (number of the the rest of all free block)
* (4.1.4) return p
* (4.2) If we can not find a free block (block size >=n), then return NULL
相關定義
根據註釋修改代碼
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {//當空閒頁不夠時,返回NULL
return NULL;
}
list_entry_t *le = &free_list;
while ((le = list_next(le)) != &free_list) {//遍歷所有指針
struct Page *p = le2page(le, page_link);//轉換爲頁結構
if (p->property >= n) {//如果找到空閒頁大小大於等於n時選中
int i;
list_entry_t *len;
for(i = 0; i < n; i++)//初始化分配內存
{
len = list_next(le);
struct Page *p2 = le2page(temp_le, page_link); //轉換頁結構
SetPageReserved(p2); //初始化標誌位
ClearPageProperty(p2);
list_del(le); //清除雙向鏈表指針
le = len;
}
if(p->property > n)
{
//若大小>n,只取大小爲n的塊
(le2page(le,page_link))->property = p->property - n;
}
ClearPageProperty(p); //初始化標誌位
SetPageReserved(p);
nr_free -= n; //空閒頁大小-n
return p;
}
}
return NULL;//分配失敗
}
default_free_pages
這個函數的作用是釋放已經使用完的頁,把他們合併到free_list
中。 具體步驟如下:
- 在free_list
中查找合適的位置以供插入
- 改變被釋放頁的標誌位,以及頭部的計數器
- 嘗試在free_list
中向高地址或低地址合併
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(!PageReserved(p) && !PageProperty(p));
p->flags = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
list_entry_t *le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
le = list_next(le);
if (base + base->property == p) {
base->property += p->property;
ClearPageProperty(p);
list_del(&(p->page_link));
}
else if (p + p->property == base) {
p->property += base->property;
ClearPageProperty(base);
base = p;
list_del(&(p->page_link));
}
}
nr_free += n;
list_add(&free_list, &(base->page_link));
}
查看註釋
* (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks.
* (5.1) according the base addr of withdrawed blocks, search free list, find the correct position
* (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before)
* (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty)
* (5.3) try to merge low addr or high addr blocks. Notice: should change some pages\'s p->property correctly.
相關定義
根據註釋修改代碼
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
assert(PageReserved(base));
list_entry_t *le = &free_list;//找合適的位置
struct Page *p = base;
while((le=list_next(le)) != &free_list)
{
p = le2page(le, page_link);
if(p > base)
{
break;
}
}
for(p = base; p < base + n; p++)//在之前插入n個空閒頁
{
list_add_before(le, &(p->page_link));
p->flags = 0;//設置標誌
set_page_ref(p, 0);
ClearPageProperty(p);
SetPageProperty(p);
}
base->property = n;//設置連續大小爲n
//如果是高位,則向高地址合併
p = le2page(le,page_link);
if(base + base->property == p )
{
base->property += p->property;
p->property = 0;
}
//如果是低位且在範圍內,則向低地址合併
le = list_prev(&(base->page_link));
p = le2page(le, page_link);
if(le != &free_list && p == base-1)//滿足條件,未分配則合併
{
while(le != &free_list)
{
if(p->property)//當連續時
{
p->property += base->property;
base->property = 0;
break;
}
le = list_prev(le);
p = le2page(le,page_link);
}
}
nr_free += n;
}
練習2
實現尋找虛擬地址對應的頁表項(需要編程)
通過設置頁表和對應的頁表項,可建立虛擬內存地址和物理內存地址的對應關係。其中的get_pte函數是設置頁表項緩解中的一個重要步驟。此函數找到一個虛地址對應的二級頁表項的內核虛地址,如果此二級頁表項不存在,則分配一個包含此項的二級頁表。
相關定義
- PDX(la): 返回虛擬地址la的頁目錄索引
- KADDR(pa): 返回物理地址pa相關的內核虛擬地址
- set_page_ref(page,1): 設置此頁被引用一次
- page2pa(page): 得到page管理的那一頁的物理地址
- struct Page * alloc_page() : 分配一頁出來
- memset(void * s, char c, size_t n) : 設置s指向地址的前面n個字節爲字節‘c’
- PTE_P 0x001 表示物理內存頁存在
- PTE_W 0x002 表示物理內存頁內容可寫
- PTE_U 0x004 表示可以讀取對應地址的物理內存頁內容
根據註釋完成代碼
//get_pte - get pte and return the kernel virtual address of this pte for la
// - if the PT contians this pte didn't exist, alloc a page for PT
// parameter:
// pgdir: the kernel virtual base address of PDT
// la: the linear address need to map
// create: a logical value to decide if alloc a page for PT
// return vaule: the kernel virtual address of this pte
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
/* LAB2 EXERCISE 2: YOUR CODE
*
* If you need to visit a physical address, please use KADDR()
* please read pmm.h for useful macros
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
* KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.
* set_page_ref(page,1) : means the page be referenced by one time
* page2pa(page): get the physical address of memory which this (struct Page *) page manages
* struct Page * alloc_page() : allocation a page
* memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s
* to the specified value c.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
* PTE_W 0x002 // page table/directory entry flags bit : Writeable
* PTE_U 0x004 // page table/directory entry flags bit : User can access
*/
//typedef uintptr_t pde_t;
pde_t *pdep = &pgdir[PDX(la)]; // (1)獲取頁表
if (!(*pdep & PTE_P)) // (2)假設頁目錄項不存在
{
struct Page *page;
if (!create || (page = alloc_page()) == NULL) // (3) check if creating is needed, then alloc page for page table
{ //假如不需要分配或是分配失敗
return NULL;
}
set_page_ref(page, 1); // (4)設置被引用1次
uintptr_t pa = page2pa(page); // (5)得到該頁物理地址
memset(KADDR(pa), 0, PGSIZE); // (6)物理地址轉虛擬地址,並初始化
*pdep = pa | PTE_U | PTE_W | PTE_P; // (7)設置可讀,可寫,存在位
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)]; // (8) return page table entry
//KADDR(PDE_ADDR(*pdep)):這部分是由頁目錄項地址得到關聯的頁表物理地址,再轉成虛擬地址
//PTX(la):返回虛擬地址la的頁表項索引
//最後返回的是虛擬地址la對應的頁表項入口地址
}
練習3
釋放某虛擬地址所在的頁並取消對應的二級頁表項的映射(需要編程)
當釋放一個包含某虛地址的物理內存頁時,需要讓對應此物理內存頁的管理數據結構Page做相關的清除處理,使得次物理內存頁成爲空閒;另外還需把表示虛地址與物理地址對應關係的二級頁表項清除
相關定義
- struct Page *page pte2page(*ptep):得到頁表項對應的那一頁
- free_page : 釋放一頁
- page_ref_dec(page) : 減少該頁的引用次數,返回剩下引用次數
- tlb_invalidate(pde_t * pgdir, uintptr_t la) : 當修改的頁表是進程正在使用的那些頁表,使之無效
//page_remove_pte - free an Page sturct which is related linear address la
// - and clean(invalidate) pte which is related linear address la
//note: PT is changed, so the TLB need to be invalidate
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
/* LAB2 EXERCISE 3: YOUR CODE
*
* Please check if ptep is valid, and tlb must be manually updated if mapping is updated
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* struct Page *page pte2page(*ptep): get the according page from the value of a ptep
* free_page : free a page
* page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.
* tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
* edited are the ones currently in use by the processor.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
*/
if (*ptep & PTE_P) //(1) check if this page table entry is present
{ //假如頁表項存在
struct Page *page = pte2page(*ptep);//(2)找到頁表項的那一頁信息
if (page_ref_dec(page) == 0)//(3)如果沒有被引用
{
free_page(page);//(4)釋放該頁
}
*ptep = 0; //(5)該頁目錄項清零
tlb_invalidate(pgdir, la); //(6) flush tlb當修改的頁表是進程正在使用的那些頁表,使之無效
}
}
運行結果
收穫
通過本次實驗,我瞭解如何發現系統中的物理內存,瞭解如何建立對物理內存的初步管理,瞭解了頁表的相關的操作,即如何建立頁表來實現虛擬內存到物理內存之間的映射,對段頁式內存管理機制有一個比較全面的瞭解。基本上在試驗中學習,根據註釋以及函數定義可以動手完成一個簡單的物理內存管理系統。完成後發現運行錯誤,通過一遍一遍的核對代碼,查閱資料,比對正確答案,終於得以修改,成功運行。