第九章 虛擬存儲器
一、虛擬存儲器提供了三個重要能力:
1、將主存看作是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保護活動的區域,並根據需要在磁盤和主存之間來回傳送數據;
2、為每個進程提供了一致的地址空間,從而簡化了存儲器管理;
3、保護了每個進程的地址空間不被其它進程破壞。
二、理解虛擬存儲器的原因:
1、虛擬存儲器是中心的:它是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的交互中心;
2、虛擬存儲器是強大的:它可以創建和銷毀存儲器片、可以映射存儲器片映射到磁盤某個部分等等;
3、虛擬存儲器若操作不當則十分危險。
9.1 物理和虛擬尋址
一、物理地址
物理尋址(PA):主存中每個字節都有唯一的物理地址;依靠此來尋址,就叫做物理尋址。
二、虛擬地址
虛擬尋址(VA):CPU生成一個虛擬地址然后用這個地址訪問主存,這個虛擬地址在送到存儲器之前先被轉換成適當的物理地址(這個過程叫做地址翻譯)。
9.2 地址空間
一、地址空間:地址空間是一個非負整數地址的有序集合:{0,1,2,……}
二、線性地址空間:地址空間中的整數是連續的。
三、虛擬地址空間:CPU從一個有 N=2^n 個地址的地址空間中生成虛擬地址,這個地址空間成為稱為虛擬地址空間。
四、地址空間的大小:由表示最大地址所需要的位數來描述。N=2^n:n位地址空間。
主存中的每個字節都有一個選自虛擬地址空間的虛擬地址和一個選自物理地址空間的物理地址。
9.3 虛擬存儲器作為緩存的工具
一、首先,VM上被組織為一個由存放在磁盤上的N個連續的字節大小的單元組成的數組。每個字節都有一個唯一的虛擬地址,這個虛擬地址是作為到數組的索引的。其次,VM系統將虛擬存儲器分割為大小固定的虛擬頁,每個虛擬頁的大小為P=2^p字節;類似地,物理存儲器被分割為物理頁(也叫做頁幀),大小也為P字節。
二、任意時刻,虛擬頁面的集合都被分為三個不相交的子集:
1、未分配的:VM系統還沒分配/創建的頁,不占用任何磁盤空間。
2、緩存的:當前緩存在物理存儲器中的已分配頁。
3、未緩存的:沒有緩存在物理存儲器中的已分配頁。
三、頁表
1、作用:將虛擬頁映射到物理頁。每次地址翻譯硬件將一個虛擬地址轉換為物理地址時都會讀取頁表。操作系統負責維護頁表中的內容。
2、結構:頁表就是一個頁表條目(PTE)數組;虛擬地址空間中的每個頁在頁表中一個固定偏移量處都有一個PTE。為了我們的目的,我們假設每個PTE是由一個有效位和一個n位的地址字段組成的。有效位表明了該物理頁的起始位置,這個物理頁中緩存了該虛擬頁。
四、缺頁
1、 DRAM緩存不命中稱為缺頁。
2、處理過程:
- 缺頁異常調用內核中的缺頁異常處理程序,該程序會選擇一個犧牲頁,將其換出內存;
- 內核從磁盤中拷貝需要的條目到犧牲頁之前所在的位置,隨后返回;
- 當異常處理程序返回之后,它會再次啟動導致缺頁的指令,該指令會把導致缺頁的虛擬地址重發送到地址翻譯硬件;
- 此時,頁面命中
3、概念補充:
- 在存儲器的習慣說法中,塊被稱為頁;
- 在磁盤和存儲器之間傳送頁的活動叫做交換或者頁面調度;
- 頁從磁盤換入DRAM和從DRAM換出磁盤;一直等待到不命中發生的時候才換入頁面;這種策略被稱為按需頁面調度
五、虛擬存儲器中的局部性
1、局部性原則保證了在任意時刻,程序將往往在一個較小的活動頁面集合上工作,這個集合叫做工作集/常駐集。
2、 顛簸:工作集大小超出了物理存儲器的大小。
9.4虛擬存儲器作為存儲器管理的工具
1、操作系統為每個進程提供了一個獨立的頁表,也就是一個獨立的虛擬地址空間。
2、抖個虛擬頁面可以映射到同一個共享物理頁面上。
3、存儲器映射:將一組連續的虛擬頁映射到任意一個文件中的任意位置的表示法。
VM簡化了鏈接和加載、代碼和數據共享,以及應用程序的存儲器分配。
9.5 虛擬存儲器作為存儲器保護的工具
PTE的三個許可位:
1、SUP:表示進程是否必須運行在內核模式下才能訪問該頁
2、READ:讀權限
3、WRITE:寫權限
9.6 地址翻譯
一、地址翻譯:形式上說,地址翻譯是一個N元素的虛擬地址空間(VAS)中的一個元素和一個M元素的物理地址空間(PAS)之間的映射;
二、過程:
- CPU中的一個控制寄存器,頁表基址寄存器指向當前頁表;
- n位的虛擬地址包括以下兩個部分:一個p位的虛擬頁面偏移和一個(n-p)位的虛擬頁號;
- MMU用后者選擇適當的PTE,再將物理頁號和虛擬地址中的VPO串聯起來得到物理地址;
- 因為物理和虛擬頁面都是P字節的,所以物理頁面偏移和VPO是相同的
三、CPU執行步驟(頁面命中)
- 處理器生成一個虛擬地址,並把它傳遞給MMU;
- MMU生成一個PTE地址,並從高速緩存/主存中請求得到它;
- 高速緩存/主存向MMU返回PTE;
- MMU構造物理地址,並把它傳給高速緩存/主存;
- 高速緩存/主存返回所請求的數據字給處理器
四、CPU執行步驟(缺頁)
- 處理器生成一個虛擬地址,並把它傳遞給MMU;
- MMU生成一個PTE地址,並從高速緩存/主存中請求得到它;
- 高速緩存/主存向MMU返回PTE;
- PTE中有效位是0,觸發了一次異常,傳遞CPU中的控制到操作系統內核中的缺頁異常處理程序;
- 缺頁處理程序確定物理存儲器中的犧牲頁,如果這個頁已經被修改了,則把它換出到磁盤;
- 缺頁處理程序調入新的頁面,並更新存儲器中的PTE;
- 缺頁處理程序返回到原來的進程,再次執行導致缺頁的指令。CPU將引起缺頁的虛擬地址重新發送給MMU;因為虛擬頁面現在緩存在物理存儲器中,所以發生命中。
9.7 案例研究
一、Core i7地址翻譯
PTE有三個權限位:
1、R/W位:確定內容是讀寫還是只讀
2、U/S位:確定是否能在用戶模式訪問該頁
3、XD位:禁止執行位,64位系統中引入,可以用來禁止從某些存儲器頁取指令
還有兩個缺頁處理程序涉及到的位:
1、A位,引用位,實現頁替換算法
2、D位,臟位,告訴是否必須寫回犧牲頁
二、Linux虛擬存儲器系統
1、 Linux為每個進程維持了一個單獨的虛擬地址空間,如圖:
2、 linux為每個進程維持了一個單獨的虛擬地址空間,其中,內核虛擬存儲器位於用戶棧之上;
3、內核虛擬存儲器包含內核中的代碼和數據結構,還有一些被映射到一組連續的物理頁面(主要是便捷地訪問特定位置,比如執行I/O操作的時候需要的位置)
4、linux將虛擬存儲器組織成一些區域(也叫做段)的集合。一個區域就是已經存在的(已分配的)虛擬存儲器的連續片;
(1)意義:允許虛擬地址空間有間隙;內核不用記錄那些不存在的頁,這樣的頁也不用占用存儲器;
(2)區域結構:
vm_start:指向起始處
vm_end:指向結束處
vm_prot:描述這個區域包含的所有頁的讀寫許可權限
vm_flags:是共享的還是私有的
vm_next:指向下一個區域
9.8 存儲器映射
一、概念:linux(以及一些其他形式的unix)通過將一個虛擬存儲器區域與一個磁盤上的對象關聯起來,以初始化這個虛擬存儲器區域的內容;
二、類型:
1、unix文件系統中的普通文件:一個區域可以映射到一個普通磁盤文件的連續部分,例如一個可執行目標文件;
2、匿名文件:一個區域也可以映射到一個匿名文件,匿名文件由內核創建,包含的全是二進制零(CPU第一次引用這樣一個區域內的虛擬頁面的時候,內核就在物理存儲器中找到合適的犧牲頁面然后用二進制零將其覆蓋)
三、共享對象和私有對象
1.共享對象
(1)共享對象對於所有把它映射到自己的虛擬存儲器進程來說都是可見的
(2)即使映射到多個共享區域,物理存儲器中也只需要存放共享對象的一個拷貝。
2.私有對象
(1)私有對象運用的技術:寫時拷貝
(2)在物理存儲器中只保存有私有對象的一份拷貝
9.9 動態存儲器分配
一、堆:是一個請求二進制0的區域,緊接在未初始化的bss區域后開始,並向上(更高的地址)生長。有一個變量brk指向堆的頂部
二、分配器的兩種基本風格:
1、顯示分配器-malloc和free
2、隱式分配器/垃圾收集器
三、為什么要使用動態存儲器分配?
因為經常知道程序實際運行時,它們才知道某些數據結構的大小。
四、分配器的要求和目標:
1、要求:
(1)處理任意請求序列
(2)立即響應請求
(3)只使用堆
(4)對齊塊
(5)不修改已分配的塊
2、目標:
(1)最大化吞吐率(吞吐率:每個單位時間里完成的請求數)
(2)最大化存儲器利用率——峰值利用率最大化
五、碎片
雖然有未使用的存儲器,但是不能用來滿足分配請求時,發生這種現象。
1、內部碎片
(1)發生在一個已分配塊比有效載荷大的時候
(2)易於量化。
2、外部碎片
(1)發生在當空閑存儲器合計起來足夠滿足一個分配請求,但是沒有一個單獨的空間塊足以處理這個請求時發生
(2)難以量化,不可預測。
六、隱式空閑鏈表
堆塊的格式:由一個字的頭部,有效荷載,和可能的額外填充組成。
七、放置已分配的塊——放置策略
1、首次適配
從頭開始搜索空閑鏈表,選擇第一個合適的空閑塊
2、下一次適配
從上一次搜索的結束位置開始搜索
3、最佳適配
檢索每個空閑塊,選擇適合所需請求大小的最小空閑塊
八、申請額外的堆存儲器
用到sbrk函數:
#include <unistd.h>
vid *sbrk(intptr_t incr);
成功則返回舊的brk指針,出錯為-1
通過將內核的brk指針增加incr來擴展和收縮堆。
九、合並空閑塊
合並是針對於假碎片問題的,任何實際的分配器都必須合並相鄰的空閑塊。
有兩種策略:
1、立即合並
2、推遲合並
十、實現簡單的分配器
實現一個簡單分配器的設計,有幾點是需要注意的:
1、序言塊和結尾塊:序言塊是初始化時創建的,而且永不釋放;結尾塊是一個特殊的塊,總是以它為結束。
2、有一個技巧,就是將重復使用的,操作復雜又有重復性的,這些可以定義成宏,方便使用也方便修改。
3、需要注意強制類型轉換,尤其是帶指針的,非常復雜。
4、因為規定了字節對齊方式為雙字,就代表塊的大小是雙字的整數倍,不是的舍入到是。
十一、顯式空閑鏈表
1、區別
(1)分配時間
隱式的,分配時間是塊總數的線性時間
顯式的,是空閑塊數量的線性時間。
(2)鏈表形式
隱式——隱式空閑鏈表
顯式——雙向鏈表,有前驅和后繼,比頭部腳部好使。
2、排序策略:
后進先出
按照地址順序維護
十二、分離的空閑鏈表
分離存儲,是一種流行的減少分配時間的方法。一般思路是將所有可能的塊大小分成一些等價類/大小類。
分配器維護着一個空閑鏈表數組,每個大小類一個空閑鏈表,按照大小的升序排列。
有兩種基本方法:
1、簡單分離存儲
每個大小類的空閑鏈表包含大小相等的塊,每個塊的大小就是這個大小類中最大元素的大小。
2、分離適配
每個空閑鏈表是和一個大小類相關聯的,並且被組織成某種類型的顯示或隱式鏈表,每個鏈表包含潛在的大小不同的塊,這些塊的大小是大小類的成員。
這種方法快速並且對存儲器使用很有效率。
分離適配的特例-----伙伴系統
其中每個大小類都是2的冪。這樣,給定地址和塊的大小,很容易計算出它的伙伴的地址,也就是說:一個塊的地址和它的伙伴的地址只有一位不同。
優點:快速檢索,快速合並。
9.10 垃圾收集
一、垃圾收集器是一種動態存儲分配器,它自動釋放程序不再需要的已分配塊,這些塊被稱為垃圾,自動回收堆存儲的過程叫做垃圾收集。垃圾收集器定期地識別垃圾塊,並相應地調用free,將這些塊放回到空閑鏈表中
二、垃圾收集器將存儲器視作一張有向可達圖,只有當存在一條從任意根節點出發並到達p的有向路徑時,才說節點p是可達的,而不可達點就是垃圾。
9.11 C程序中常見的與存儲器有關的錯誤
1、間接引用壞指針
2、讀未初始化的存儲器
3、允許棧緩沖區溢出
4、假設指針和它們指向的對象是相同大小的
5、造成錯位錯誤
6、引用指針,而不是它所指向的對象
7、誤解指針運算
8、引用不存在的變量
9、引用空堆塊中的數據
10、引起存儲器泄露
學習進度條
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一周 | 0/0 | 1/2 | 20/20 | 學會了虛擬機安裝和Ubuntu的基本操作 |
第二周 | 56/56 | 1/3 | 20/40 | 學會了Ubuntu終端下的C語言編寫 |
第三周 | 110/166 | 1/4 | 30/70 | 熟悉了gdb的基本操作,了解了計算機信息表示和處理 |
第四周 | 0/166 | 1/5 | 10/80 | 復習了前幾周的知識 |
第五周 | 42/208 | 2/6 | 30/110 | 學習了linux下的匯編語言內容 |
第六周 | 216/424 | 1/7 | 30/140 | linux下Y86指令集 |
第七周 | 71/495 | 1/8 | 20/160 | 學習了局部性原理和緩存思想的應用 |
第八周 | 0/495 | 2/10 | 20/180 | 復習了之前的學習內容並總結 |
第九周 | 133/628 | 2/12 | 20/200 | 學習了系統級I/O的內容,了解了函數的內在 |
第十周 | 407/1035 | 1/13 | 30/230 | 對代碼進行分析調試,系統級的I/O內容 |
第十一周 | 714/1749 | 2/15 | 40/270 | 了解異常及其種類 |
第十二周 | 0/1749 | 4/19 | 30/300 | 復習前幾周內容 |
第十三周 | 797/2728 | 2/21 | 20/320 | 學習了多線程的基本內容,並發編程 |
第十四周 | 0/2728 | 1/22 | 15/335 | 虛擬存儲器 |