操作系統lab3實驗報告
本次實驗主要完成ucore內核對虛擬內存的管理工作。其總體設計思路還是比較簡單,即首先完成初始化虛擬內存管理機制,即需要設置好哪些頁需要放在物理內存中,哪些頁不需要放在物理內存中,而是可被換出到硬盤上,並涉及完善建立頁表映射、頁錯誤異常處理操作等函數實現。然後就執行一組訪存測試,看看我們建立的頁表項是否能夠正確完成虛實地址映射,是否正確描述了虛擬內存頁在物理內存中還是在硬盤上,是否能夠正確把虛擬內存頁在物理內存和硬盤之間進行傳遞,是否正確實現了頁面替換算法等。
練習0 填寫已有實驗
同樣與lab2類似,我使用了名爲meld
的一款文件比對工具,直接比對lab2
和lab3
兩個文件夾,它就能把其中不相同的部分列舉出來,然後比對進行修改即可。大致截圖如下:
比對修改之後,大致羅列一下有以下文件需要我們進行修改:
default_pmm.c
pmm.c
trap.c
練習0主要要讓我們修改補充的就是上述四個文件。直接在meld
裏面對比複製就行了。也沒有什麼技術含量。
練習1 給未被映射的地址映射上物理頁
本實驗要求完成 do_pgfault
函數,作用給未被映射的地址映射上物理頁。
具體而言,當啓動分頁機制以後,如果一條指令或數據的虛擬地址所對應的物理頁框不在內
存中或者訪問的類型有錯誤(比如寫一個只讀頁或用戶態程序訪問內核態的數據等),就會發生
頁錯誤異常。產生頁面異常的原因主要有:
- 目標頁面不存在(頁表項全爲0,即該線性地址與物理地址尚未建立映射或者已經撤銷);
- 相應的物理頁面不在內存中(頁表項非空,但Present標誌位=0,比如在swap分區或磁盤文件上)
- 訪問權限不符合(此時頁表項P標誌=1,比如企圖寫只讀頁面).
當出現上面情況之一,那麼就會產生頁面page fault(#PF)
異常。產生異常的線性地址存儲在
CR2中,並且將是page fault
的產生類型保存在 error code 中
那麼我們的這個do_pgfault
函數的思路就明顯了。do_pgfault()
函數從CR2寄存器中獲取頁錯誤異常的虛擬地址,根據error code
來查找這個虛擬地址是否在某一個VMA的地址範圍內,那麼就給它分配一個物理頁。
這裏的VMA是描述應用程序對虛擬內存“需求”的變量,如下:
struct vma_struct {
// the set of vma using the same PDT
struct mm_struct *vm_mm;
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma
uint32_t vm_flags; // flags of vma
//linear list link which sorted by start addr of vma
list_entry_t list_link;
};
vm_start
和vm_end
描述的是一個合理的地址空間範圍(即嚴格確保 vm_start < vm_end的關係);list_link
是一個雙向鏈表,按照從小到大的順序把一系列用vma_struct
表示的虛擬內存空間鏈接起來,並且還要求這些鏈起來的vma_struct
應該是不相交的,即vma之間的地址空間無交集;vm_flags
表示了這個虛擬內存空間的屬性,目前的屬性包括> #define VM_READ 0x00000001 //只讀 #define VM_WRITE 0x00000002 //可讀寫 #define VM_EXEC 0x00000004 //可執行
vm_mm
是一個指針,指向一個比vma_struct
更高的抽象層次的數據結構mm_struct
而這mm_struct
包含所有虛擬內存空間的共同屬性,如下:
struct mm_struct {
// linear list link which sorted by start addr of vma
list_entry_t mmap_list;
// current accessed vma, used for speed purpose
struct vma_struct *mmap_cache;
pde_t *pgdir; // the PDT of these vma
int map_count; // the count of these vma
void *sm_priv; // the private data for swap manager
};
mmap_list
是雙向鏈表頭,鏈接了所有屬於同一頁目錄表的虛擬內存空間mmap_cache
是指向當前正在使用的虛擬內存空間pgdir
所指向的就是mm_struct
數據結構所維護的頁表map_count
記錄mmap_list
裏面鏈接的vma_struct
的個數sm_priv
指向用來鏈接記錄頁訪問情況的鏈表頭
最後具體的實現如下:(這裏只摘錄了我填寫的部分)
if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) { //目標頁面不存在,失敗
cprintf("get_pte in do_pgfault failed\n");
goto failed;
}
if (*ptep == 0) { //權限不夠,也是失敗!
if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
cprintf("pgdir_alloc_page in do_pgfault failed\n");
goto failed;
}
}
else { //頁表項非空,可以嘗試換入頁面
if(swap_init_ok) {
struct Page *page=NULL;//根據mm結構和addr地址,嘗試將硬盤中的內容換入至page中
if ((ret = swap_in(mm, addr, &page)) != 0) {
cprintf("swap_in in do_pgfault failed\n");
goto failed;
}
page_insert(mm->pgdir, page, addr, perm);//建立虛擬地址和物理地址之間的對應關係
swap_map_swappable(mm, addr, page, 1);//將此頁面設置爲可交換的
page->pra_vaddr = addr;
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}
練習2 補充完成基於FIFO()
的頁面替換算法
頁錯誤異常發生時,有可能是因爲頁面保存在swap區或者磁盤文件上造成的,所以我們需要通過頁面分配解決這個問題。
頁面替換主要分爲兩個方面,頁面換出和頁面換入。
- 頁面換入主要在上述的
do_pgfault()
函數實現; - 頁面換出主要在
swap_out_vistim()
函數實現。
這裏換入在練習1已經完成了,這裏就主要介紹換出。
FIFO
替換算法會維護一個隊列,隊列按照頁面調用的次序排列,越早被加載到內存的頁面會越早被換出。
具體實現的函數如下:
首先是_fifo_map_swappable()
,它的主要作用是將最近被用到的頁面添加到算法所維護的次序隊列。
static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) {
list_entry_t *head=(list_entry_t*) mm->sm_priv;
list_entry_t *entry=&(page->pra_page_link);
assert(entry != NULL && head != NULL);
list_add(head, entry); //將最近用到的頁面添加到次序隊尾
return 0;
}
然後是_fifo_swap_out_victim()
函數是用來查詢哪個頁面需要被換出,它的主要作用是用來查詢哪個頁面需要被換出。
static int
_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) {
list_entry_t *head=(list_entry_t*) mm->sm_priv;
assert(head != NULL);
assert(in_tick==0);
list_entry_t *le = head->prev; //用le指示需要被換出的頁
assert(head!=le);
struct Page *p = le2page(le, pra_page_link);//le2page宏可以根據鏈表元素獲得對應的Page指針p
list_del(le); //將進來最早的頁面從隊列中刪除
assert(p !=NULL);
*ptr_page = p; //將這一頁的地址存儲在ptr_page中
return 0;
}
結果截圖
由於在虛擬機內部,而且滾屏稍快,所以這裏截圖如下:
這裏可以看到如下部分:
........
check_vma_struct() succeeded!
page fault at 0x00000100: K/W [no page found].
check_pgfault() succeeded!
check_vmm() succeeded.
........
然後是下圖:
可以看到這個
check_swap() succeeded!
所以實驗成功