思考題
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能不能做得更輕松一些。