本實驗依賴實驗1。請把你作的實驗1的代碼填入本實驗中代碼中有「LAB1」的註釋相應部分。提示:可採用diff和patch工具進行半自動的合併(merge),也可用一些圖形化的比較/merge工具來手動合併,好比meld,eclipse中的diff/merge工具,understand中的diff/merge工具等。算法
其實 lab1 中只有 kern/debug/kdebug.c kern/init/init.c kern/trap/trap.c 被改過了 因此只用把這三個文件複製到lab2中就能夠了。
在實現first fit 內存分配算法的回收函數時,要考慮地址連續的空閒塊之間的合併操做。提示:在創建空閒頁塊鏈表時,須要按照空閒頁塊起始地址來排序,造成一個有序的鏈表。可能會修改default_pmm.c中的default_init,default_init_memmap,default_alloc_pages, default_free_pages等相關函數。請仔細查看和理解default_pmm.c中的註釋。編程
查看註釋後,發現代碼已經寫了,因而make qemu 了一發,發現錯了,斷言失敗。仔細看了後,代碼是不完整的。數組
斷言失敗以前的檢查都是針對頁分配成功、失敗的測試,從失敗的斷言這裏開始是對分配的頁的位置進行的檢查。緩存
實際上,最初的實現 default_*_pages 只是簡單的插入到鏈表,根本沒有關心被插入到了哪裏,因此在屢次 alloc/free 以後頁塊在鏈表中的位置亂得一塌糊塗。若是須要保序,須要對每一個改動鏈表的地方注意一下位置。數據結構
struct Page { // 頁幀的 引用計數 int ref; // 頁幀的狀態 Reserve 表示是否被內核保留 另外一個是 表示是否 可分配 uint32_t flags; // 記錄連續空閒頁塊的數量 只在第一塊進行設置 unsigned int property; // 用於將全部的頁幀串在一個雙向鏈表中 這個地方頗有趣 直接將 Page 這個結構體加入鏈表中會有點浪費空間 所以在 Page 中設置一個鏈表的結點 將其結點加入到鏈表中 還原的方法是將 鏈表中的 page_link 的地址 減去它所在的結構體中的偏移 就獲得了 Page 的起始地址 list_entry_t page_link; }; // 初始化空閒頁塊鏈表 static void default_init(void) { list_init(&free_list); nr_free = 0; // 空閒頁塊一開始是0個 } // 初始化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; set_page_ref(p, 0); } base->property = n; // 頭一個空閒頁塊 要設置數量 SetPageProperty(base); nr_free += n; // 初始化玩每一個空閒頁後 將其要插入到鏈表每次都插入到節點前面 由於是按地址排序 list_add_before(&free_list, &(base->page_link)); } // 分配n個頁塊 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; // 查找 n 個或以上 空閒頁塊 若找到 則判斷是否大過 n 則將其拆分 並將拆分後的剩下的空閒頁塊加回到鏈表中 while ((le = list_next(le)) != &free_list) { // 此處 le2page 就是將 le 的地址 - page_link 在 Page 的偏移 從而找到 Page 的地址 struct Page *p = le2page(le, page_link); if (p->property >= n) { page = p; break; } } if (page != NULL) { if (page->property > n) { struct Page *p = page + n; p->property = page->property - n; SetPageProperty(p); // 將多出來的插入到 被分配掉的頁塊 後面 list_add(&(page->page_link), &(p->page_link)); } // 最後在空閒頁鏈表中刪除掉原來的空閒頁 list_del(&(page->page_link)); nr_free -= n; ClearPageProperty(page); } return page; } // 釋放掉 n 個 頁塊 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; le = list_next(&free_list); // 將合併好的合適的頁塊添加回空閒頁塊鏈表 while (le != &free_list) { p = le2page(le, page_link); if (base + base->property <= p) { break; } le = list_next(le); } list_add_before(le, &(base->page_link)); }
成功後會提示:
而後會在另外一個地方斷言掛掉,那是接下來的實驗了。eclipse
Question 1
你的first fit算法是否有進一步的改進空間函數
固然啦,這種東西(對於一個區間中某些數字的存在性維護)用線段樹能夠實現 alloc 和 free 達到 O(log n) 級別的時間複雜度,不過空間(複雜度雖然仍是 O(n) 不變)要增長一倍。工具
經過設置頁表和對應的頁表項,可創建虛擬內存地址和物理內存地址的對應關係。其中的get_pte函數是設置頁表項環節中的一個重要步驟。此函數找到一個虛地址對應的二級頁表項的內核虛地址,若是此二級頁表項不存在,則分配一個包含此項的二級頁表。本練習須要補全get_pte函數 in kern/mm/pmm.c,實現其功能。請仔細查看和理解get_pte函數中的註釋。get_pte函數的調用關係圖以下所示:
測試
具體是在 check_pgdir 函數中一個斷言 get_pte 返回值失敗的,看到 get_pte 函數,就是第二個練習須要編寫修改的地方。ui
get_pte ,是給出 Page Directory 基址和物理地址,求對應的 Page Table Entry 地址。
pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create) { pde_t *pdep = &pgdir[PDX(la)]; // 找到 PDE 這裏的 pgdir 能夠看作是 頁目錄表的基址 if (!(*pdep & PTE_P)) { // 看看 PDE 指向的頁表 是否存在 struct Page* page = alloc_page(); // 不存在就申請一頁物理頁 /* 經過 default_alloc_pages() 分配的頁 的地址 並非真正的頁分配的地址 實際上只是 Page 這個結構體所在的地址而已 故而須要 經過使用 page2pa() 將 Page 這個結構體 的地址 轉換成真正的物理頁地址的線性地址 而後須要注意的是 不管是 * 或是 memset 都是對虛擬地址進行操做的 因此須要將 真正的物理頁地址再轉換成 內核虛擬地址 */ if (!create || page == NULL) { return NULL; } set_page_ref(page, 1); uintptr_t pa = page2pa(page); memset(KADDR(pa), 0, PGSIZE); // 將這一頁清空 此時將 線性地址轉換爲內核虛擬地址 *pdep = pa | PTE_U | PTE_W | PTE_P; // 設置 PDE 權限 } return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)]; }
首先註釋中的 pdep ( Page Directory Entry Pointer ),就是 PD 基質加上 PDE 編號,編號就是 LA 的高十位( x86 的約定),能夠經過 PDX 宏獲取。
而後檢查是否設置了 Present 位,也就是 PDE_P 位。不過實際上並無 PDE_P 這個宏,使用註釋裏告訴咱們的等價的 PTE_P 來檢查(註釋裏告訴咱們這個位是 PDE 和 PTE 通用的,雖然這三個位的確是通用的,不過畢竟 PD 和 PT 的保留位仍是有所不一樣的)。
若是沒有設置並且 bool create 設置爲須要申請,就須要按註釋 (3) ~ (7) 中提示通常的 alloc 一個頁、設置引用計數、得到線性地址、使用 memset 清空頁內容、設置 PDE 項中權限位。
最後再從 la 中獲取一下中間十位,也就是 PTE 編號,從 PD 獲取一下基址,相加就是 PTE 的線性地址了,用 KADDR 處理一下便可。
請回答以下問題:
請描述頁目錄項(Pag Director Entry)和頁表(Page Table Entry)中每一個組成部分的含義和以及對ucore而言的潛在用處。
PDE 詳解
從低到高,分別是:
P (Present) 位:表示該頁保存在物理內存中。
R (Read/Write) 位:表示該頁可讀可寫。
U (User) 位:表示該頁能夠被任何權限用戶訪問。
W (Write Through) 位:表示 CPU 能夠直寫回內存。
D (Cache Disable) 位:表示不須要被 CPU 緩存。
A (Access) 位:表示該頁被寫過。
S (Size) 位:表示一個頁 4MB 。
9-11 位保留給 OS 使用。
12-31 位指明 PTE 基質地址。
PTE 詳解
從低到高,分別是:
0-3 位同 PDE。
C (Cache Disable) 位:同 PDE D 位。
A (Access) 位:同 PDE 。
D (Dirty) 位:表示該頁被寫過。
G (Global) 位:表示在 CR3 寄存器更新時無需刷新 TLB 中關於該頁的地址。
9-11 位保留給 OS 使用。
12-31 位指明物理頁基址。
若是ucore執行過程當中訪問內存,出現了頁訪問異常,請問硬件要作哪些事情?
進行換頁操做 首先 CPU 將產生頁訪問異常的線性地址 放到 cr2 寄存器中 ;而後就是和普通的中斷同樣 保護現場 將寄存器的值壓入棧中 ;而後壓入 error_code 中斷服務例程將外存的數據換到內存中來 ;最後 退出中斷回到進入中斷前的狀態。
當釋放一個包含某虛地址的物理內存頁時,須要讓對應此物理內存頁的管理數據結構Page作相關的清除處理,使得此物理內存頁成爲空閒;另外還需把表示虛地址與物理地址對應關係的二級頁表項清除。請仔細查看和理解page_remove_pte函數中的註釋。爲此,須要補全在 kern/mm/pmm.c中的page_remove_pte函數。page_remove_pte函數的調用關係圖以下所示:
以前斷言失敗是由於在 page_insert 中調用了 page_remove_pte ,而 page_remove_pte 並無修改引用計數因此致使了對於引用計數的斷言失敗。
static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { if ((*ptep & PTE_P)) { struct Page *page = pte2page(*ptep); if (page_ref_dec(page) == 0) { // 若引用計數減一後爲0 則釋放該物理頁 free_page(page); } *ptep = 0; // 清空 PTE tlb_invalidate(pgdir, la); // 刷新 tlb } }
首先判斷一下 ptep 是否是合法——檢查一下 Present 位就是了;而後經過註釋中所說的,經過 pte2page(*ptep) 獲取相應頁,減小引用計數並決定是否釋放頁;最後把 TLB 中該頁的緩存刷掉就能夠了。
數據結構Page的全局變量(實際上是一個數組)的每一項與頁表中的頁目錄項和頁表項有無對應關係?若是有,其對應關係是啥?
能夠參照 kern/mm/pmm.h 中的轉換函數。其實就是 Page 全局數組中以 Page Directory Index 和 Page Table Index 的組合 PPN (Physical Page Number) 爲索引的那一項。
剩下的不會;
參考連接:
[1].https://yuerer.com/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-uCore-Lab-2/
[2].https://xr1s.me/2018/05/27/ucore-lab2-report/