lab2——記憶體管理
思考題
Thinking 2.1
請思考cache用虛擬地址來查詢的可能性,並且給出這種方式對訪存帶來的好處和壞處。另外,你能否能根據前一個問題的解答來得出用實體地址來查詢的優勢?
用虛擬地址查詢的好處是不需要經過tlb和頁表查詢,如果cache命中的話極大地提高了效能。壞處是多個虛擬地址有可能對應同樣的實體地址,且虛擬地址的大小一般比實體地址大,所以對於同樣大小的cache採用虛擬地址查詢會導致命中率的下降,導致cache替換,效率可能更低。另外,對於每個程序都有自己獨立的虛擬空間,不可能給每個程序配備一個cache,而使用同一個cache又會面臨相同虛擬地址可能對應不同實體地址的問題。
實體地址查詢的優勢就是沒有上面的壞處,總體來說效能和命中率更高。
Thinking 2.2
請查閱相關資料,針對我們提出的疑問,給出一個上述流程的優化版本,新的版本需要有更快的訪存效率。(提示:考慮並行執行某些步驟)
查詢TLB的同時進行頁表查詢工作。如果TLB命中則提前終止頁表查詢;如果TLB沒有命中的話,得到實體地址後,更新TLB的同時查詢cache。
Thinking 2.3
在我們的實驗中,有許多對虛擬地址或者實體地址操作的巨集函式(詳見include/mmu.h ),那麼我們在呼叫這些巨集的時候需要弄清楚需要操作的地址是實體地址還是虛擬地址,閱讀下面的程式碼,指出x是一個實體地址還是虛擬地址。
int x; char *value = return_a_pointer(); *value = 10; x = (int) value;
虛擬地址。因為value是一個指標,而在c語言中指標的地址使用的都是虛擬地址
Thinking 2.4
我們在 include/queue.h 中定義了一系列的巨集函式來簡化對連結串列的操作。實際上,我們在 include/queue.h 檔案中定義的連結串列和 glibc 相關原始碼較為相似,這一連結串列設計也應用於 Linux 系統中 (sys/queue.h 檔案)。請閱讀這些巨集函式的程式碼,說說它們的原理和巧妙之處。
巨集函式中涉及到的連結串列名稱、資料型別、成員變數都沒有具體指出,而是以引數的形式出現並使用。這樣的好處是在呼叫這些巨集函式的時候,可以適應各種情況而不需要進行重複定義
Thinking 2.5
我們注意到我們把巨集函式的函式體寫成了 do { /* ... */ } while(0)的形式,而不是僅僅寫成形如 { /* ... */ } 的語句塊,這樣的寫法好處是什麼?
引用自https://www.cnblogs.com/lanxuezaipiao/p/3535626.html
do{...}while(0)在C中是唯一的構造程式,讓你定義的巨集總是以相同的方式工作,這樣不管怎麼使用巨集(尤其在沒有用大括號包圍呼叫巨集的語句),巨集後面的分號也是相同的效果。
如果沒有do while(0)結構的話,在if語句中巨集定義會被展開成多條語句,從而出現邏輯錯誤
do能確保大括號裡的邏輯能被執行,while(0)能確保該邏輯只執行一次
Thinking 2.6
注意,我們定義的 Page 結構體只是一個資訊的載體,它只代表了相應實體記憶體頁的資訊,它本身並不是實體記憶體頁。 那我們的實體記憶體頁究竟在哪呢?Page 結構體又是通過怎樣的方式找到它代表的實體記憶體頁的地址呢? 請你閱讀 include/pmap.h 與 mm/pmap.c 中相關程式碼,給出你的想法。
我們的物理頁所在的實體地址等於 物理頁號左移12位。Page結構體通過page2ppn計算當前頁的物理頁號,再用過page2pa函式得到page的真實實體地址
Thinking 2.7
請閱讀 include/queue.h 以及 include/pmap.h, 將Page_list的結構梳理清楚,選擇正確的展開結構(請注意指標)。
C
A:
struct Page_list{
struct {
struct {
struct Page *le_next;
struct Page **le_prev;
}* pp_link;
u_short pp_ref;
}* lh_first;
}
B:
struct Page_list{
struct {
struct {
struct Page *le_next;
struct Page **le_prev;
} pp_link;
u_short pp_ref;
} lh_first;
}
C:
struct Page_list{ LIST_HEAD
struct { Page
struct { LIST_ENTRY
struct Page *le_next;
struct Page **le_prev;
} pp_link;
u_short pp_ref;
}* lh_first;
}
Thinking 2.9
瞭解了二級頁表頁目錄自對映的原理之後,我們知道,Win2k核心的虛存管理也是採用了二級頁表的形式,其頁表所佔的 4M 空間對應的虛存起始地址為 0xC0000000,那麼,它的頁目錄的起始地址是多少呢?
頁目錄相對於頁表起始地址的偏移量為 0xC0000000 >> 10 = 0x00030000
頁目錄的虛擬地址起始值是 0x00030000+0xC0000000=0xC0030000
Thinking 2.10
注意到頁表在程序地址空間中連續存放,併線性對映到整個地址空間,思考: 是否可以由虛擬地址直接得到對應頁表項的虛擬地址?上一節末尾所述轉換過程中,第一步查頁目錄有必要嗎,為什麼?
不能。有必要,只有通過查詢頁目錄才能知道這個頁面是否有效
Thinking 2.11
TLB彙編函式
#include <asm/regdef.h>
#include <asm/cp0regdef.h>
#include <asm/asm.h>
LEAF(tlb_out)
nop
mfc0 k1,CP0_ENTRYHI
mtc0 a0,CP0_ENTRYHI
nop
// insert tlbp or tlbwi
nop
nop
nop
nop
mfc0 k0,CP0_INDEX
bltz k0,NOFOUND
nop
mtc0 zero,CP0_ENTRYHI
mtc0 zero,CP0_ENTRYLO0
nop
// insert tlbp or tlbwi
NOFOUND:
mtc0 k1,CP0_ENTRYHI
j ra
nop
END(tlb_out)
思考一下tlb_out 彙編函式,結合程式碼闡述一下跳轉到NOFOUND的流程?從MIPS手冊中查詢tlbp和tlbwi指令,明確其用途,並解釋為何第10行處指令後有4條nop指令。
從ENTRYHI中讀取tlb的資訊並將其修改,用tlbp尋找對應tlb的入口並更新INDEX
根據INDEX的結果得知是否找到,如果沒有跳轉至NOFOUND
tlbp尋找tlb中一個對應的入口
tlbwi把Index暫存器的值作為索引寫入一個tlb入口
四條nop是因為tlbp更新了CPO_INDEX的值,而mfc0又需要用到更新後的CP0_INDEX的結果,所以只能等待四條指令
Thinking 2.12
u_long* va = 0x12450;
u_long* pa;
page_insert(boot_pgdir, pp, va, PTE_R);
pa = va2pa(boot_pgdir, va);
printf("va: %x -> pa: %x\n", va, pa);
*va = 0x88888;
printf("va value: %x\n", *va);
printf("pa value: %x\n", *((u_long *)((u_long)pa + (u_long)ULIM)));
這段程式碼旨在計算出相應va與pa的對應關係,設定許可權位為PTE_R是為了能夠將資料寫入記憶體。
如果MMU能夠正常工作,實際輸出將會是:
va: 12450 -> pa: 3ffd000
va value: 88888
pa value: 0
page_check() succeeded!
顯然,執行後結果與我們預期的不符,va值為0x88888,相應的pa中的值為0。這說明我們的程式碼中存在問題,請你仔細思考我們的訪存模型,指出問題所在。
答:因為va的地址不是頁面積的整數倍,而在va2pa
的過程中,會被強制轉化為物理頁面的起始地址。低12位清零。所以在執行va = 0x88888
的過程中,並沒有把這個值賦到pa所在的位置,所以pa的值仍然是0
Thinking 2.13
在X86體系結構下的作業系統,有一個特殊的暫存器CR4,在其中有一個PSE位,當該位設為1時將開啟4MB大物理頁面模式,請查閱相關資料,說明當PSE開啟時的頁表組織形式與我們當前的頁表組織形式的區別。
答:PSE開啟時直接用連續的4MB儲存1024個頁表,通過高十位找到對應頁表,再用接下來10位頁表索引找到具體的物理頁面
實驗難點圖示
難點一
在練習2.5中使用的PTE_V作為有效標誌位給我帶來了很大的困擾,我以為根據命名規則這應該是表示PTE型別的表格內容是否有效的,因而誤以為需要分配實體記憶體並把實體地址填寫到二級頁表中。並且沒有想到PTE_V可以同時作為頁目錄和頁表中的有效標誌位,想當然地以為只能作為兩者之一的有效標誌,所以花費了很長時間思考另一個標誌位是什麼。
還有對於查詢後未命中並且也不置create=1的情況該如何處理也糾結了很長時間。
本次實驗之所以花費了大量的時間,我覺得還是因為沒法理解提供的程式碼的意思。很多變數雖然有了定義但是沒有相應的說明,並不能準確知道這些變數的用途。
難點二
另一個卡了我幾個小時的bug就是在pgdir_walk
的時候沒有給pp_ref加一。也是因為慣性思維,基本模仿了boot_pgdir_walk
的寫法,忘記了因為此時呼叫了page_alloc
抽出了一頁空閒頁表使用,所以需求修改相應的pp_ref。
體會與感想
這次實驗的難度明顯高於lab1和lab0,花費的總時間達到了驚人的28小時,讓人感到非常受折磨。
說實話理論課上關於二級頁表的知識感覺並不難,對於實驗總體上需要幹什麼基本也是清楚的。但一落實到具體每一個函式應該怎麼寫的時候,關於每個變數的含義、各種函式的使用就產生了諸多問題。debug的過程因為不熟悉gxemul和tmux的功能也非常吃力。時間好像莫名奇妙就在debug的過程中消失了。
這次算是勉強通過了,不知道下個lab能不能做得更輕鬆一些。