分層存儲器體系:高速緩存、內存、硬盤
存儲管理器:管理分層存儲器體系
最底層高速緩存管理由硬件完成,本章講解對內存管理
對磁盤抽象和管理,文件系統部分在講。
3.1 無存儲器抽象
存儲器模型就是物理內存。
無法同時運行兩個程序
在不使用存儲器抽象的情況下運行多個程序
將當前內存中所有內容保存到磁盤,將下個程序讀入內存即可。
後面略過感覺上古機器不重要(希望憋打臉
3.2 一種存儲器抽象:地址空間
直接使用物理地址缺點
- 操作系統容易被破壞
- 執行多個程序困難
3.2.1 地址空間
地址空間:進程可用於尋址內存的一套地址集合。每個進程有一個自己的地址空間,獨立於其他進程(除非特殊情況)
重定位:把每個進程的地址空間映射到物理內存不同部分。
一個簡單實現重定位方法,基址寄存器與界限寄存器
每個 CPU 配置兩個特殊硬件寄存器
裝載程序:裝載到內存中連續的空閒位置且裝載期間無需重定位。
當一個程序運行時,程序的起始物理地址裝載到『基址寄存器』,程序的長度裝載到『界限寄存器』
每次訪問內存(取一條指令,讀或寫一個數據字)CPU硬件會把地址發送到內存總線前,自動把基址值加到進程發出的地址值,並檢查地址值是否大於界限寄存器裏的值。
使用 基址寄存器與界限寄存器 重定位缺點,每次訪問需要加法和比較運算,加法運算沒有特殊電路的情況會比較慢。
3.2.2 交換技術
內存常常不夠用,內存超載兩種通常處理方法:
- 交換技術:將進程存進磁盤
- 虛擬內存:該方法甚至能在程序只有一部分被調入內存的情況下運行。
下面先討論交換技術
換入內存,會進行重定位
內存緊縮:交換內存會產生空閒區,通過將所有進程儘可能向下移動,將空閒區合併,會耗費大量CPU時間
有個問題,進程被創建或換入時應該分配多大內存?
一種方法,當換入或移動進程,爲他分配一些額外的內存。進程交換到磁盤時,只交換實際使用的內存中的內容。
3.2.3 空閒內存管理
跟蹤內存使用情況一般有兩種方法:
- 位圖
- 空閒區鏈表
- 使用位圖的存儲管理
內存被劃分成若干分配單元,每個分配單元對應位圖中一位,0 表示空閒,1 表示佔用(或者相反)。
分配單元越小,位圖越大。若進程大小不是分配單元整數倍,存在內存浪費。
位圖缺點慢,如加載一個進程需要 k 位 0 串,無法快速搜索。
- 使用鏈表的存儲管理
維護一個記錄已分配內存段和空閒內存段的鏈表。其中鏈表一個節點或者包含一個進程,或者包含兩個進程j間的一塊空閒區。
每個結點包含:一個P(進程)/H(空閒區)的指示符,起始地址,長度,下一結點指針。
雙向鏈表更合適,以便檢查上個結點是否可以合併。
創建進程(或從磁盤換入進程)方法,假設知道爲進程分配多少內存
- 首次適配法,找到第一個足夠大的空閒區,快因爲儘可能少搜索鏈表
- 下次適配法,和『首次適配法』比較接近,不同的是每次找到合適空閒區就記錄當前位置,以便下次尋找,性能略低於首次適配法
- 最佳適配算法,搜索整個鏈表,找到最能容納進程的最小空閒區域。比『首次適配法』慢,並且浪費內存更多(驚),因爲產生大量無用小空閒區
- 最差適配算法,總是分配最大的可用空閒區,仿真程序表明該算法也不太行
進程和空閒區維護各自獨立的鏈表,可以提高創建的效率,但降低內存釋放的效率。
分離的保存空閒區的鏈表可用做個優化,利用空閒區存儲節點信息
快速適配算法
爲常用大小的空閒區,再維護一個鏈表。這樣可以快速找到一個指定大小的空閒區,但內存釋放費時。
3.3 虛擬內存
基本思想:每個程序擁有自己的地址空間,這個空間被分割成多個塊,每一個塊稱作『一頁』或『頁面』,每一頁有連續的地址範圍,這些頁被映射到物理內存,但並不是所有的頁都必須在內存中才能運行程序。
當程序引用到不在物理內存中的地址空間時,由操作系統將缺失的部分裝入物理內存並重新執行失敗命令。
虛擬內存適合多道程序設計系統,程序等待讀入內存時,可以把 CPU 交給另一個進程使用。
3.3.1 分頁
程序產生的地址稱爲『虛擬地址』。使用虛擬內存的情況,虛擬地址被送到內存管理單元(MMU),MMU將虛擬地址映射爲物理內存地址。
頁面:虛擬地址按照固定空間大小劃分成的單元
頁框:頁面在物理內存中對應的單元
頁面和頁框大小通常一樣。
RAM 和磁盤之間的交換以整個頁面爲單元進行。
操作系統支持對不同大小頁面的混合使用匹配。
缺頁中斷(缺頁錯誤):訪問頁面沒有被映射的虛擬地址
操作系統會找一個很少使用的頁框且把他內容寫入磁盤(如果它不在磁盤上),隨後把所需要訪問的頁面讀到剛纔回收的頁框中,修改映射關係,重新啓動引起陷阱的指令。
3.3.2 頁表
虛擬地址被分成『虛擬頁號』(高位部分)和『偏移量』(低位部分)
虛擬頁號作爲『頁表』的索引
例如對於 16 位地址 和 4KB 的頁面大小,高四位可以指定 16 個虛擬頁面,低 12 位確定所選頁面字節偏移量
虛擬頁號可用作頁表的索引,以找到該虛擬頁面對應的頁表項。由頁表項找到頁框號(如果有),再用頁框號拼接到偏移量高位替代虛擬頁號,形成送往內存的物理地址。
從數學角度說,頁表是一個函數,通過這個函數可以把虛擬地址頁面域替換成頁框域,形成物理地址。
PS:我的感受
a 虛擬地址
b[] 頁表
例如前四位是虛擬頁號
c = ((1<<12)-1) = 0000 1111 1111 1111
d = ~c = 1111 0000 0000 0000)
物理地址 = (b[(a&d)>>12]<<12) | (a&c)
頁表項結構
頁框號
『在/不在』位
保護位(如三位,讀、寫、執行)
修改位:寫入一頁時硬件自動設置修改位,可以用來判斷頁面是否被修改過,沒有可以直接丟棄,因爲磁盤上副本仍在
訪問位:讀和寫都會設置訪問位。被用來幫助操作系統發生缺頁中斷時選擇淘汰頁面。
高速緩存禁止位:禁止該頁面被告訴緩存,比如內存映射的 I/O 機器需要這一位(這位沒搞懂)
需要注意
某個頁面不在內存中,保存該頁面磁盤地址由操作系統內部的軟件表格記錄,而不是頁表。
3.3.3 加速分頁過程
分頁系統需要主要考慮的兩個問題
- 虛擬地址到物理地址的映射速度
- 虛擬地址空間太大,頁表也會很大
最簡單的設計是使用由『快速硬件寄存器』陣列組成的單一頁表,每一個表項對應一個虛擬頁面,虛擬頁號作爲索引。啓動一個進程,操作系統把保存在內存中的進程頁表副本載入到寄存器,不必再爲頁表訪問內存。
優點:簡單、映射快
缺點:頁表很大時,代價高,每次上下文切換都必須裝載頁表(每次切換進程都要載入)
另個極端方法
將整個頁表存在內存中,這樣只需要一個指向頁表起始頁的寄存器。
缺點:上下文切換快,但平時其他操作慢了,需要多次讀取內存。
1. 輪換檢查緩衝區
一種現象:大多數程序總是對很少量的頁面進行多次訪問。因此只有很少的頁表項會被反覆讀取,大多數頁表項很少被訪問。
可以考慮,爲計算機設置一個小型的硬件設備『轉換檢測緩衝區(TLB)』又稱『相聯存儲器』、『快表』,將虛擬地址直接映射到物理地址,而不必再訪問頁表。
該設備存儲少量的表項
大致流程:MMU 優先從 TLB 中搜索,無法找到再從頁表中搜索。
2. 軟件TLB管理
目前爲止,假設一臺具有虛擬內存的機器,都由硬件識別頁表,以及一個 TLB。MMU 硬件管理 TLB。只有內存中沒找到某頁面纔會陷入操作系統中。
現在許多現代機器,幾乎所有的頁面管理都是在軟件中實現。TLB 表項被操作系統顯示裝載,當發生 TLB 訪問失效,由操作系統解決。
TLB 失效比缺頁中斷髮生得更頻繁
TLB 變大可以減少失效率,TLB 軟件管理變得足夠有效
使用軟件管理的好處,MMU 會變得非常簡單,爲高速緩存及其他性能改善騰出空間
兩種 TLB 失效
軟失效:頁面訪問在內存中而不在 TLB 中,更新一下 TLB 即可,不產生磁盤I/O,快。
硬失效:頁面訪問在磁盤中,需要從磁盤存取以裝入該頁面,慢。
頁表遍歷:頁表結構中查找相應的映射
程序訪問非法地址,不需要向 TLB 中新增映射,此時,操作系統會報告『段錯誤』終止進程,屬於程序錯誤。
3.3.4 針對大內存的頁表
1. 多級頁表
多級頁表避免全部頁表一直保存在內存中。
例子
32 位虛擬地址分爲 10 位 PT1 域、10 位 PT2 域、 12 位 Offset(偏移量)域。
用 PT1 在頂級頁表中找到二級頁表地址,用 PT2 在二級頁表中找到頁框號
另種尋址實現形式:頁目錄指針表,擴展頁表項位,處理更大的地址空間。
2. 倒排頁表
書沒怎麼看懂,看了眼網圖,大概是用 hashmap
將虛擬頁號存儲,多記錄一個進程 ID,查找時只需要在 hashmap
以進程 ID 爲區分找到某一哈希值下的某一進程即可。
這樣可能會很慢,哈希的時候值都一樣就成鏈了
倒排頁表在 64 位機器中比較常見
3.4 頁面置換算法
發生缺頁中斷,操作系統從內存中選擇一個頁面將其換出內存,爲調入內存騰空間。
如果換出頁面在內存駐留期間被修改過,必須寫回磁盤以更新磁盤的副本。
如果沒被修改過,不需要寫回。
頁面置換算法解決該淘汰哪個頁面。
3.4.1 最優頁面置換算法
知道每個頁面將在多久以後使用,所以不可能實現。然後根據時間長短替換(越長越優先被置換)。
該算法雖然不能實現,但是可以用來當其他算法上限,使得其他算法之間比較有個量化指標。
3.4.2 最近未使用頁面置換算法『NUR』
- R = 0,M = 0
- R = 0,M = 1
- R = 1,M = 0
- R = 1,M = 1
從編號小的類中隨機選一個置換。(看到總結纔看到這條信息)
隱藏意思:淘汰一個沒有被訪問已修改頁面 優於 被頻繁讀取無修改的頁面
優點:易於理解和能夠有效實現
性能不行
3.4.3 先進先出頁面置換算法『FIFO』
實現一個鏈表,最新進入放鏈尾,從鏈表頭開始淘汰。
發生頁面中斷時淘汰最早的頁面
3.4.4 第二次機會頁面置換算法
先進先出算法的改進。
如果最早頁面的 R 位是 1,將 R 位清 0 重新放入鏈表尾部。如果最早頁面 R 位是 0 直接置換。
3.4.5 時鐘頁面置換算法
環形鏈表實現的『第二次機會頁面置換算法』,錶針指向當前檢查頁面
置換直接把新頁面替換,再指針向前移動一位。
R位是 1 ,就清楚 R 位,在指針向前移動一位
3.4.6 最近最少使用頁面置換算法『LRU』
在缺頁中斷髮生時,置換未使用時間最長的頁面。
硬件要求高,需要知道各個頁面各有多久時間未被進程訪問,以及快速找到最近最久未使用的頁面
3.4.7 用軟件模擬 LRU『NFU』
前面一種硬件實現,支持的計算機比較少。所以這裏提供一個軟件實現的解決方案『NFU』算法。
爲每個頁面與一個軟件計數器關聯,計數器初值爲 0,每次時鐘中斷掃描內存中所有頁面的 R 位加到計算器上,發生缺頁中斷,置換計數器值最小頁面。
NFU 不會忘記,可能之前常常出現後面不會出現,所以提供老化算法。每次修改,計數器先右移一位再將R值加入左端位。
NFU 算法與 LRU 區別
每次修改,不是及時的,期間會有多個頁面已經被訪問。
老化算法計數器位有限
3.4.8 工作集頁面置換算法
顛簸:每執行幾條指令程序就發生一次缺頁中斷。
工作集模型:爲了降低中斷率,不少分頁系統都會設法跟蹤進程的工作集,以確保在讓進程運行以前,工作集已在內存中了。
預先調頁:在程序運行前預先裝入其工作集頁面
發生缺頁中斷時,淘汰一個不在工作集中的頁面。
工作集確定:最近 k 次訪問所使用過的頁面的集合
設想 k 位寄存器類似先進先出的頁面置換算法維護頁號。
不好維護沒實現過,存在近似方法。
另種工作集確定:過去一段時間內內存訪問所用到的頁面集合。
容易實現,每個進程計算自己執行時間
表項至少包含:上次使用該頁面近似時間、訪問位『R』
找到 R 是 0 的表示當前時鐘滴答中該頁面沒有被訪問,可以作爲候選者。然後計算多久沒被使用,用一個值比較,大了就置換它。
全都找了找不到從候選者裏找,候選者空的隨機刪除(最好是乾淨頁面)
3.4.9 工作集時鐘頁面置換算法『WSClock』
性能好、實現簡單、實際工作中廣泛應用
數據結構是循環表,記錄:上次使用時間、R 位,M位
與時鐘算法類似,缺頁中斷,先檢查指針指向頁面(掃描中記錄乾淨頁面位置)。
- R 位是 1,將 R 位值 0 不淘汰,指向下個指針重複該步驟。
- R 位是 0,如果生存時間大於閾值且頁面乾淨,它就不在工作集。
- 如果頁面不乾淨(修改過),指針繼續向前走。
- 走了一圈
- 至少調度一次寫操作,置換第一個遇到的乾淨頁面,因爲最終會有某個寫操作完成。
- 沒有調度寫操作,隨便置換一個乾淨頁面,沒有乾淨頁面置換當前頁面。
沒搞懂寫操作位怎麼清 0 的?不應該是置換後才能清 0 嗎?以後再看書再說,沒時間了。
3.4.10 小結
3.5 分頁系統中的設計問題
頁面置換算法一直沒有考慮,相互競爭的程序之間如何分配內存。
全局的頁面置換算法通常情況下比局部頁面置換算法好。
全局算法時,系統不停確定每個進程應該分配多少頁框。
PFF 算法 :管理內存動態分配,指出何時增加或減少分配給一個進程的頁面,沒有說明缺頁中斷應該替換哪個。
PFF 假定:缺頁中斷率隨着分配的頁面增加而降低。
PFF儘可能讓每個進程缺頁中斷率控制在可接受範圍內。
3.5.2 負載控制
將一部分進程交換到磁盤以減少競爭內存數。
決定交換出哪個進程需要考慮:
- 進程大小
- 分頁率(啥叫分頁率)
- 特性(CPU 密集型、I/O 密集型)
- 以及其他進程的特性
3.5.3 頁面大小
確定頁面大小的矛盾有如下
小頁面的優:
進程的內部碎片(進程的內存空間未被利用部分)少,大尺寸頁面比小尺寸頁面浪費了更多內存。
小頁面更能充分利用TLB空間
小頁面的劣:
更大的頁表
磁盤和內存交換大部分時間花在尋道和旋轉延遲上,所以傳輸頁面的大小影響不大,數量影響更大
切換進程時硬件寄存器裝入頁表花費時間長
操作系統有時會爲系統中不同部分使用不同頁面的大小,如內核大頁面,用戶空間小頁面
3.5.4 分離的指令空間和數據空間
指令(程序正文):I 空間
數據:D 空間
鏈接器必須知道如何使用分離的空間
3.5.5 共享頁面
如多個用戶執行同一個程序,共享頁面效率更高,不是所有頁面都適合共享。
只讀頁面(諸如程序文本)可以共享,數據頁面不能共享。
如果系統支持分離指令空間和數據空間,多個進程共享程序將變得簡單。每個進程的進程表有兩個指針一個指向 I 空間頁表,一個指向 D 空間頁表。
即使沒用支持分離空間,進程也可以共享程序(有時爲庫),但機制複雜。
在兩個進程 A、B 同時運行一個編輯器並共享頁面,如果調度程序決定從內存移走 A,撤銷其頁面填充其他程序,那 B 會引起大量缺頁中斷。需要專門的數據結構記錄共享頁面。
如果共享單元是單個頁面而不是整個頁表,更加複雜,但不是不可能。如 UNIX 系統中,fork 系統調用後,父進程和子進程共享程序文本和數據。分頁系統通常讓這些進程分別擁有他們自己的頁表,但是指向同一個頁面集合。當然頁面都是隻讀的。
寫時複製:當一個進程更新了一點數據,觸發只讀保護,生成該頁副本,每個進程都有自己的專用副本。兩個複製都是可讀寫的。對副本修改不會觸發陷阱。
3.5.6 共享庫
現代操作系統,許多大型庫被衆多進程使用。所以需要『共享庫』(DDL 或 動態鏈接庫)
未定義外部函數:任何在目標文件中被調用但沒用被定義的函數(如 printf),未定義外部函數調用但不存在的函數也是未定義外部函數(printf 調用 write)
傳統的鏈接,鏈接一個程序時,鏈接器的命令中指定一個或多個目標文件,可能包括庫文件。鏈接器會在庫中尋找未定義外部函數。如果找到,則將它們加載到可執行二進制文件中。當鏈接器完成任務,一個可執行二進制文件被寫入磁盤,其中包含了所需全部函數,庫中定義但是沒被調用的函數不會被加載進去。當程序被裝入內存執行時,所需所有函數都已經準備就緒。
有些庫被衆多程序使用照常內存浪費,所以引入共享庫。
當一個程序和共享庫鏈接時,鏈接器沒有加載被調用的函數,而是加載一小段能夠在運行時被調用函數綁定的『存根例程』
依賴與系統和配置信息,共享庫或者和程序一起被裝載,或者在其所包含函數第一次被調用時裝載。整個庫根據需要以頁面爲單位裝載。
共享庫優點
- 使可執行文件更小、更省內存空間
- 共享庫中一個函數因爲 BUG 更新,不需要重新編譯調用了這個函數的程序。舊的二進制文件依然可以工作
問題:共享庫的頁面怎麼被程序定位
編譯共享庫時,用一個特殊編譯選項高速編譯器,不要產生使用決定地址的指令,只能使用相對地址的指令。
使用相對偏移量的代碼稱作位置無關代碼。
3.5.7 內存映射文件
思想:進程可以通過發起一個系統調用,將一個文件映射到虛擬地址空間的一部分。
內存映射文件提供一種 I/O 可選模型,把一個文件當作一個內存中的大字符數組來訪問,而不用通過讀寫操作來訪問。
當多個進程同時映射同一個文件,就可以通過共享內存來通信。
3.5.8 清除策略
分頁守護進程:爲保證有足夠的空閒頁框,大多時間在睡眠,定期喚醒檢查內存狀態。空閒頁框過少,分頁進程通過預定的頁面置換算法選擇頁面換出內存,如果頁面被修改過則寫入內存。
如果需要一個已被淘汰頁面,如果該頁框未被覆蓋,將其從空閒頁框緩衝池中移出即可恢復該頁面。
另種實現清楚策略方法
使用雙指針時鐘。
前指針由分頁守護進程控制。當它指向一個髒頁面,就把該頁面寫回磁盤,然後繼續向前移動指針。
後指針用於頁面置換,這樣可以提高後指針命中乾淨頁面的概率。
3.5.9 虛擬內存接口
某些高級系統,程序員可以對內存映射進行控制,並通過非常規方法增強程序的行爲。
允許程序員對內存映射進行控制的原因
爲了允許多個進程共享同一部分內存。高帶寬共享成爲可能。
複製數據更快
3.6 有關實現的問題
實現虛擬內存系統要在主要的理論算法之間選擇,還需要注意一些實際問題。
3.6.1 與分頁有關工作
操作系統在下面四段時間進行分頁相關工作
- 進程創建時
- 進程執行時
- 缺頁中斷時
- 進程終止時
創建新進程
操作系統需要確定程序和數據初始有多大,還需要創建頁表,爲頁表分配空間及初始化。
操作系統要在磁盤交換區中分配空間,以便在進程交換出時磁盤上有放置此進程的空間
我覺得這裏對我來說不是很重要,但又很細跳了跳了。
3.7 分段
目前討論的虛擬內存都是一維的,但對許多問題來說,有多個獨立的地址空間可能比只有一個好得多。
爲了程序和數據可以被劃分爲邏輯上獨立的地址空間並且有助於共享和保護,發明『段』。
需要一種令程序員不用管理表擴張和收縮的方法。
段:機器上提供多個相互獨立的地址空間,每個段構成一個獨立的地址空間。
段是一個邏輯實體,可能包括一個過程、一個數組、一個堆棧,一般不包括多種不同類型的內容。
每個段長度可以不同,段長度運行期間也會動態改變。所以它們可以獨立地增長或減小而不影響到其他的段。
分段更容易實現共享庫。純分頁系統也可以實現但是複雜,實際上是模擬分段實現。
不同段可以有不同的類型保護,如只讀,只執行。
3.7.1 純分段實現
分頁定長,分段不定長
這部分感覺目前對我用處不大跳過
3.8 有關內存管理的研究
3.9 小結
分頁 - 交換技術 - 虛擬內存 - 頁面置換算法 - 內存分配策略 - 分段