lab2實驗報告
實驗思考題
2.1
請思考cache用虛擬地址來查詢的可能性,並且給出這種方式對訪存帶來的好處和壞處。另外,你能否能根據前一個問題的解答來得出用物理地址來查詢的優勢?
cache用虛擬地址查詢時可能的,只要CPU(程序)可以用虛擬地址取到正確物理地址中的數據,其中間經過的cache使用的地址並無大礙。
使用虛擬地址的cache時,優點在於查詢cache前不用訪問頁表進行地址轉換,缺點在於一旦cache中數據缺失,更新cache時需要訪問頁表,時間開銷更大;同時多個程序的虛擬地址可能相同,安全性以及數據正確性比較難以保障,異或每個程序單獨cache會對數據共享造成影響。
運用物理地址查詢的優勢便在於數據的安全性和方便共享。
2.2
請查閱相關資料,針對我們提出的疑問,給出一個上述流程的優化版本,新的版本需要有更快的訪存效率。(提示:考慮並行執行某些步驟)
訪問TLB出現缺失時,要更新TLB,但在更新TLB時訪問頁表的過程中我們需要的物理地址就已經可以獲得了。因此可以並行更新TLB和查詢cache。
2.3
在我們的實驗中,有許多對虛擬地址或者物理地址操作的宏函數(詳見include/mmu.h ),那么我們在調用這些宏的時候需要弄清楚需要操作的地址是物理地址還是虛擬地址,閱讀下面的代碼,指出x是一個物理地址還是虛擬地址。
int x;
char *value = return_a_pointer();
*value = 10;
x = (int) value;
x的值是虛擬地址
2.4
我們在 include/queue.h 中定義了一系列的宏函數來簡化對鏈表的操作。實際上,我們在 include/queue.h 文件中定義的鏈表和 glibc 相關源碼較為相似,這一鏈表設計也應用於 Linux 系統中 (sys/queue.h 文件)。請閱讀這些宏函數的代碼,說說它們的原理和巧妙之處。
- 這些宏函數用於創建及操作鏈表,包括了創建鏈表頭、初始化鏈表頭、清空鏈表、頭插入、尾插入、中間項前后的插入、刪除特定元素的操作。其鏈表頭定義為了結構體,存儲內容為頭指針,而后對於每個節點,在其內部嵌套結構體用於存儲下一項的地址和前一項中存放下一項的指針的地址,從而實現了鏈表的功能。
- 其巧妙之處之一是通過運用結構體定義實現了鏈表內容的可變性。鏈表頭實現了自定義名稱;鏈表節點可以是任意類型,其中存放前后節點信息的部分用結構體封裝后作為鏈表節點的一部分而在使用宏時完全不受其他部分影響。
- 巧妙之處之二時鏈表節點信息中存放了前一項中存放下一項的指針的地址,從而更加方便了插入和移出節點的操作。這樣可以在只知道一個鏈表節點的情況下對鏈表進行插入或刪除節點操作。
2.5
我們注意到我們把宏函數的函數體寫成了 do { /* ... */ } while(0)的形式,而不是僅僅寫成形如 { /* ... */ } 的語句塊,這樣的寫法好處是什么?
這樣寫可以保證宏在替換到程序當中時不會產生歧義或錯誤,使其是一個完整的語句塊,相當於一條語句而不是多條語句,用起來更加方便。
2.6
注意,我們定義的 Page 結構體只是一個信息的載體,它只代表了相應物理內存頁的信息,它本身並不是物理內存頁。 那我們的物理內存頁究竟在哪呢?Page 結構體又是通過怎樣的方式找到它代表的物理內存頁的地址呢? 請你閱讀 include/pmap.h 與 mm/pmap.c 中相關代碼,給出你的想法。
根據pmap.h中關於轉換物理地址的函數代碼:
static inline u_long
page2ppn(struct Page *pp)
{
return pp - pages;
}
/* Get the physical address of Page 'pp'.
*/
static inline u_long
page2pa(struct Page *pp)
{
return page2ppn(pp) << PGSHIFT;
}
可以發現計算物理地址主要用到的數據是指針pp相對於pages的偏移量。
閱讀pmap.c可以發現pages定義為Page結構體的指針,容易知道其為一個數組的頭指針,數組中的每一項順序對應一塊內存區域,通過辨別在數組中的項數即可對應物理內存頁。
2.7
請閱讀 include/queue.h 以及 include/pmap.h, 將Page\_list的結構梳理清楚,選擇正確的展開結構(請注意指針)。
其為C:
struct Page_list{
struct {
struct {
struct Page *le_next;
struct Page **le_prev;
} pp_link;
u_short pp_ref;
}* lh_first;
2.8
在 mmu.h 中定義了 bzero(void *b, size_t) 這樣一個函數,請你思考,此處的b指針是一個物理地址, 還是一個虛擬地址呢?
根據pmap.c中對這個函數的應用我們可以知道b指針是一個虛擬地址。
2.9
了解了二級頁表頁目錄自映射的原理之后,我們知道,Win2k內核的虛存管理也是采用了二級頁表的形式,其頁表所占的 4M 空間對應的虛存起始地址為 0xC0000000,那么,它的頁目錄的起始地址是多少呢?
0xC0300000
2.10
注意到頁表在進程地址空間中連續存放,並線性映射到整個地址空間,思考:是否可以由虛擬地址直接得到對應頁表項的虛擬地址?上一節末尾所述轉換過程中,第一步查頁目錄有必要嗎,為什么?
可以直接由虛擬地址得到頁表項地址,取消二級頁表的設定,只使用一級頁表;或是通過轉換計算亦可。
設置頁目錄可以節約頁表的內存開銷,十分必要。其同樣可以用於判斷也表內內容是否有效。
2.11
思考一下tlb_out 匯編函數,結合代碼闡述一下跳轉到NOFOUND的流程?從MIPS手冊中查找tlbp和tlbwi指令,明確其用途,並解釋為何第10行處指令后有4條nop指令。
回答見下注釋
#include <asm/regdef.h>
#include <asm/cp0regdef.h>
#include <asm/asm.h>
LEAF(tlb_out)
//1: j 1b
nop
//把CP0_ENTRYHI原有值存儲到$k1中
mfc0 k1,CP0_ENTRYHI
//把a0中值存放到CP0_ENTRYHI;
//CP0_ENTRYHI存放了虛擬地址空間及其標志位
mtc0 a0,CP0_ENTRYHI
nop
//查詢CP0_ENTRYHI中虛擬地址是否存在TLB中:
//如果有則把匹配項的index保存到Index寄存器中;
//沒有匹配則置Index的最高位為1.
tlbp
//nop用於等待tlbp執行完畢(流水線暫停)
nop
nop
nop
nop
//讀取改寫后的CP0_INDEX到$k0
mfc0 k0,CP0_INDEX
//如果$k0中值小於0,即CP0_INDEX最高位置1,即TLB缺失
//則跳轉到NOFOUND
bltz k0,NOFOUND
nop
//清空CP0_ENTRYHI和CP0_ENTRYLOW
mtc0 zero,CP0_ENTRYHI
mtc0 zero,CP0_ENTRYLO0
nop
//更新TLB
tlbwi
NOFOUND:
mtc0 k1,CP0_ENTRYHI
j ra
nop
END(tlb_out)
2.12
顯然,運行后結果與我們預期的不符,va值為0x88888,相應的pa中的值為0。這說明我們的代碼中存在問題,請你仔細思考我們的訪存模型,指出問題所在。
運用va2pa()只是獲得了va對應物理內存中的頁的首地址,並未獲得實際的va對應的pa地址,即未考慮頁內偏移量。
2.13
在X86體系結構下的操作系統,有一個特殊的寄存器CR4,在其中有一個PSE位,當該位設為1時將開啟4MB大物理頁面模式,請查閱相關資料,說明當PSE開啟時的頁表組織形式與我們當前的頁表組織形式的區別。
當開啟PSE時,頁的划分與當前基本一致,但在一級頁表中增加使用了一位用於標識,用於區分從一級頁表中尋找得到的地址是二級頁表的入口地址還是4MB大小的頁面地址。由於一級頁表中一項能夠映射到的地址本就是4MB,所以整體結構變化不大。
實驗難點圖示
- 實驗前半程我們填寫了鏈表操作用的宏、內存初始化、空閑鏈表建立和頁面的分配和刪除。其中最難的個人認為是理解為鏈表定義的一系列宏。每個鏈表節點存放了*le_next和**le_prev,這樣方便了對鏈表的改變,但理解它到底怎么用花費了大量時間,主要在於理解這個指針的指針。用圖來表示一下,同列的變量共用一塊存儲空間:
(this是本節點、pre是上一節點,表示法與c語言語法不一致)
這樣在改變鏈表時,運用本節點的le_prev可以直接對上一節點的le_next存儲值進行改變。 - 實驗后半程完成了頁表的創建、物理內存與虛擬內存建立映射和更新頁表。個人認為難點在於辨別物理地址、虛擬地址和空閑鏈表中節點的地址。
其中空閑鏈表中節點的地址是page數組中的一項,其相對數組頭的偏移量與物理地址相對首地址的位移相對應;
物理地址和虛擬地址則通過所學知識在不同區域用不同方法轉換。
重點就在於區分操作數到底擁有的是哪個地址。
實驗感想
綜述
本次實驗相比於前幾次有了明顯的難度提升,花費在其上的時間成倍提升,從周一下午開始幾乎一直工作到周三中午。自己對於前半部分實驗(2.1-2.4)部分所花費的時間明顯多於后部分,一方面在於閱讀代碼是積累的過程,前面要閱讀更多代碼,后面則是應用,另一方面是自己對於指針的操作不夠熟練。
體會
- 有時候需要更多相信自己
自己總是在思考上花費大量時間而不敢動手寫代碼。總是怕自己有些部分沒想對,抑或是理解錯誤。預設的一些變量沒有用到則會更加害怕。但一路寫下來也並沒有出現太多錯誤。 - 操作系統啟動是一層套一層慢慢向上累加
在對頁面操作時,我們用到了兩套函數,一套在沒有空閑鏈表時使用,按位操作,一套則是在為自己創造環境之后按頁面進行操作。這讓我想起了真正系統啟動時的不同階段。操作系統真的是在一點點給自己創造工作環境。