ucore作業系統lab2實驗報告

cs_assult發表於2015-06-14

練習一、first-fit連續記憶體分配

檔案:default_pmm.c

(一)思路:

首先我們需要用一個資料結構來描述每個物理頁(也稱頁幀),這裡用了雙向連結串列結構來表示每個頁。連結串列頭用 free_area_t結構來表示,包含了一個 list_entry 結構的雙向連結串列指標和記錄當前空閒頁的個數的無符號整型變數 nr_free

typedef struct {

list_entry_t free_list;  // the list header

unsigned int nr_free;  // # of free pages in this free list

} free_area_t;

接下來需要了解管理物理頁的Page資料結構,這個資料結構也是實現連續實體記憶體分配演算法的關鍵資料結構,可通過此資料結構來完成空閒塊的連結和資訊儲存,而基於這個資料結構的管理物理頁陣列起始地址就是全域性變數pages

struct Page {

    int ref;                        // 對映此物理頁的虛擬頁個數

    uint32_t flags;                 // 物理頁屬性(空或不空

unsigned int property;          // 連續空頁有多少(只在地址最低頁有值,其餘為0)

list_entry_t page_link;  // 雙向連結各個Page結構的page_link雙向連結串列(用於釋放)

};

實體記憶體頁管理器順著雙向連結串列進行搜尋空閒記憶體區域,直到找到一個足夠大的空閒區域,這是一種速度很快的演算法,因為它儘可能少地搜尋連結串列。如果空閒區域的大小和申請分配的大小正好一樣,則把這個空閒區域分配出去,成功返回;否則將該空閒區分為兩部分,一部分割槽域與申請分配的大小相等,把它分配出去,剩下的一部分割槽域形成新的空閒區。其釋放記憶體的設計思路很簡單,只需把這塊區域重新放回雙向連結串列中即可。      

(二)任務:

修改default_init_memmap()default_alloc_pages()default_free_pages()函式。  

(三)實現

1、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;
        SetPageProperty(p);
		//清零此頁的引用計數 
        set_page_ref(p, 0);
        //將空閒頁插入到連結串列
		list_add_before(&free_list, &(p->page_link)); 
    }
    base->property = n;
    //計算空閒頁總數 
    nr_free += n;
}

2、default_alloc_pages()

此函式是用於為程式分配空閒頁。 其分配的步驟如下: 

 尋找足夠大的空閒塊 ,如果找到了,重新設定標誌位

②從空閒連結串列中刪除此頁 

③判斷空閒塊大小是否合適 ,如果不合適,分割頁塊 ,如果合適則不進行操作 

 計算剩餘空閒頁個數 

 返回分配的頁塊地址 

static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0);
    if (n > nr_free) {
        return NULL;
    }
    list_entry_t  *len;
list_entry_t  *le = &free_list;
//在空閒連結串列中尋找合適大小的頁塊
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);
//找到了合適大小的頁塊
        if (p->property >= n) {
int i;
for(i=0;i<n;i++){
len = list_next(le);
//讓pp指向分配的那一頁
//le2page巨集可以根據連結串列元素獲得對應的Page指標p
struct Page *pp = le2page(temp_le, page_link);
//設定每一頁的標誌位
SetPageReserved(pp);
ClearPageProperty(pp);
//清除free_list中的連結
list_del(le);
le = len;
}
if(p->property>n){
//分割的頁需要重新設定空閒大小
(le2page(le,page_link))->property = p->property - n;
}
//第一頁重置標誌位
ClearPageProperty(p);
SetPageReserved(p);
nr_free -= n;
return p;
}
}
//否則分配失敗
    return NULL;
}

2、default_free_pages()

這個函式的作用是釋放已經使用完的頁,把他們合併到free_list中。 具體步驟如下:  

①在free_list中查詢合適的位置以供插入 

②改變被釋放頁的標誌位,以及頭部的計數器 

③嘗試在free_list中向高地址或低地址合併

static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
assert(PageReserved(base));
struct Page *p = base;
//查詢該插入的位置le
list_entry_t *le = &free_list;
while((le=list_next(le)) != &free_list){
p = le2page(le, page_link);
if(p>base) break;
}
//向le之前插入n個頁(空閒),並設定標誌位
    for (p = base;p<base+n;p++) {
        list_add_before(le, &(p->page_link));
        p->flags = 0;
        set_page_ref(p, 0);
ClearPageProperty(p);
SetPageProperty(p);
}
//將頁塊資訊記錄在頭部
    base->property = n;
//是否需要合併
//向高地址合併
p = le2page(le, page_link);      
   	if (base + n == p) {
            base->property += p->property;
            list_del(&(p->page_link));
}
//向低地址合併
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;
}

練習二、查詢虛擬地址對應頁表項

(一)、思路:

pde_t全稱為 page directory entry,也就是一級頁表的表項(注意:pgdir實際不是表 項,而是一級頁表本身。實際上應該新定義一個型別pgd_t來表示一級頁表本身)。pte t全 稱為 page table entry,表示二級頁表的表項。uintptr t表示為線性地址,由於段式管理只做直接對映,所以它也是邏輯地址。

pgdir給出頁表起始地址。通過查詢這個頁表,我們需要給出二級頁表中對應項的地址。 雖然目前我們只有boot_pgdir一個頁表,但是引入程式的概念之後每個程式都會有自己的頁 表。

有可能根本就沒有對應的二級頁表的情況,所以二級頁表不必要一開始就分配,而是等到需要的時候再新增對應的二級頁表。如果在查詢二級頁表項時,發現對應的二級頁表不存在,則需要根據create引數的值來處理是否建立新的二級頁表。如果create引數為0,則get_pte返回NULL;如果create引數不為0,則get_pte需要申請一個新的物理頁(通過alloc_page來實現,可在mm/pmm.h中找到它的定義),再在一級頁表中新增頁目錄項指向表示二級頁表的新物理頁。注意,新申請的頁必須全部設定為零,因為這個頁所代表的虛擬地址都沒有被對映。

當建立從一級頁表到二級頁表的對映時,需要注意設定控制位。這裡應該設定同時設定 上PTE_UPTE_WPTE_P(定義可在mm/mmu.h)。如果原來就有二級頁表,或者新建立了頁表,則只需返回對應項的地址即可。

(二)、實現:

pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
    /* 
     * 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 pte_t;
    pde_t *pdep = &pgdir[PDX(la)];   // (1) find page directory entry
    //若獲取不成功則執行下面的語句
if (!(*pdep & PTE_P)) {             
    	//申請一頁
struct Page *page;
if(!creat || (page = all_page())==NULL){
return NULL;
} 
//引用次數需要加1
set_page_ref(page, 1);
//獲取頁的線性地址                   
        uintptr_t pa = page2pa(page); 
memset(KADDR(pa), 0, PGSIZE);
        //設定許可權
*pdep  = pa | PTE_U | PTE_W | PTE_P;                 
}
//返回頁表地址
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];          
}

練習三、釋放虛擬地址所在頁,並取消對應二級頁表對映

(一)、思路:

判斷此頁被引用的次數,如果僅僅被引用一次,則這個頁也可以被釋放。否則,只能釋放頁表入口。

(二)、實現:

static inline voidpage_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
/* 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
     */    	
<span style="white-space:pre">	</span>//判斷頁表是否存在
	if (*ptep & PTE_P){         
<span style="white-space:pre">		</span>struct Page* page = pte2page(*ptep);
	//判斷此頁是否被多次引用         
<span style="white-space:pre">	</span>if (page_ref_dec(page)==0){
		free_page(page);        
<span style="white-space:pre">	</span>}        
<span style="white-space:pre">	</span>*ptep = 0;
	//釋放pte        
<span style="white-space:pre">	</span>tlb_invalidate(pgdir, la);    <span style="font-family: 宋體; font-size: 10.5pt; letter-spacing: 0pt; line-height: 19.5pt; text-indent: 0pt;">}}</span>

執行結果如下圖:











相關文章