虛存子系統是全部 UNIX 系統的核心組件。下面討論虛存系統的實現及其對操做系統中幾乎其餘全部子系統的做用和影響。首先詳細說明一些基本的內存管理問題;而後具體分析 Linux 操做系統如何實施虛存管理任務。進程(也標記爲任務或默認線程)經過虛存子系統可以查看地址空間中的線性字節範圍,這個功能與物理內存中的物理布
局或者分片狀況無關。 線程能夠在一個呈現爲
CPU
所有地址空間的虛擬環境中執行。這種
(
支撐
)
執行框架爲進程提供了一種大型編程模型。在這種情形下,做爲地址空間的內存虛擬視圖被呈現給應用,而虛存子系統透明地管理虛擬存儲器基礎架構,從而對物理內存子系統以及輔助存儲器加以一致管理
。
內核映像部分也稱爲標識映射段,而內核模塊部分常常稱爲頁表映射段。對高端地址空間(0xFFFF ..)的訪問機制與平臺相關。在 32位系統上,每一個進程的虛址空間爲 4GB;而在 64 位系統上,每一個進程在理論上的虛址空間大小2^64一般並未所有利用。 某些系統(實質上是真正的處理器)只容許每一個進程的虛址空間大小爲 2^44。web
1、內存與地址空間
算法
因爲物理內存子系統的延遲低於磁盤子系統, 虛存子系統所面臨的挑戰之一是將訪問最頻繁的內存部分保持在速度更快的主存中。當物理內存短缺時, 虛存子系統須要釋放出部分物理內存。這經過將較少使用的內存頁面輸出到備份存儲器上來完成。所以,進程無需管理物理內存分配的細節,從這個意義上說, 虛存子系統提供了資源虛擬化功能。進程也無需對信息和故障的隔離加以管理,由於每一個進程都在本身的地址空間中執行。大多數狀況下,經過阻止進程訪問其合法地址空間以外的內存, 內存管理部件中的硬件機制能夠執行內存保護功能。其例外情形是在多個進程之間顯式共享的內存區域。編程
一、地址空間
進程虛址空間的定義是做爲運行環境呈現給進程的內存地址範圍。在進程生命週期的任什麼時候間點上,都會有一些進程地址被映射到物理地址,而另外一些進程地址則不被映射。 當對fork()系統調用進行初始化時, 內核建立進程虛址空間的基本框架。進程內部的虛址空間佈局由動態連接器創建,能夠隨着硬件平臺的不一樣而變化。通常地, 虛址空間
由稱爲虛擬頁面的同等大小的內存容量構成。在IA-32環境中,頁面大小爲4KB;在IA-64體系結構中, 頁面大小可配置爲 4KB、 8KB、 16KB或 64KB。 任何 Linux進程的虛址空間又進一步劃分爲兩個主要區域: 用戶空間和內核空間。 用戶空間駐留在地址空間的較低部分,從地址零開始,其上限爲在 processor.h中規定的與平臺有關TASK_SIZE取值。 其他地址空間則保留給內核。 地址空間的用戶部分被標記爲私有的, 這代表它由進程本身的頁表加以映射;另外一方面, 內核空間則由全部進程共享。根據硬件基礎架構的不一樣, 內核地址空間或者映射到每一個進程地址空間的高端部分, 或者佔用 CPU虛址空間的頂端部分。在用戶級上執行操做時,只能訪問到用戶地址空間,由於對內核虛址進行操做會致使保護違規錯誤(protection violation fault);而在內核模式中執行操做時, 用戶空間和內核空間都是可訪問的。
二、用戶地址空間
在 Linux內核中,每一個地址空間都經過一個稱爲 mm結構的對象來表示。因爲多個任務可能共享同一個地址空間, mm結構是一個只要引用數目大於零就存在的引用計數(referencecounted)對象。每一個任務的數據結構都包含 mm指針, 指向定義了該任務(進程)的地址空間的 mm結構。
下圖所示,進程試圖讀取地址 z處的一個字。實際的讀操做如序號 1所示。因爲頁表假定爲空的,該讀操做會致使一個頁面故障。爲了響應這個頁面故障, Linux內核會搜索該特定進程的 VM區域(area)列表, 以便定位包含該故障地址的 VM區域。在肯定了針對該特定請求必須訪問的頁面以後, Linux發起一個磁盤文件讀操做,如序號 2所示。當I/O子系統提供該文件後, 操做系統將數據複製到一個可用的頁幀中,如序號3所示。完成這個讀頁面故障處理所需的最後步驟是對頁表進行更新以便將虛址映射至包含數據的物理頁幀。以後系統能夠從新初始化這個讀請求。此時該請求將成功完成,由於所需的數據已經可用。
諸如 kswapd或 pdflush線程等只訪問內核地址空間的任務使用了一個匿名的地址空間,所以這些狀況下的 mm指針引用值爲 NULL。由於 mm結構包含了兩個用於創建虛存環境的主要數據結構指針,因此被看做是虛存子系統核心的入口點。第一個結構是頁表,第二個結構稱爲虛存區域。從內核的角度而言, 系統範圍內的頁表足以實現虛存機制。 一些更傳統的大型頁表, 包括分簇頁表機制, 在表示大型地址空間時的效率並不高。
三、VM區域
爲了不大型頁錶帶來的問題, Linux並不使用頁表自己來表示地址空間,而是使用了 VM區域結構的一組列表。 該方法的思想是將一塊地址空間劃分紅可按照相同方式處理的多個連續頁面範圍, 其中每一個範圍均可以經過單個 VM區域結構來表示。 若是進程訪問一個在頁表中沒有轉換項的頁面, 負責該特定頁面的 VM區域擁有創建和安裝該頁
面所需的全部信息。如圖 上圖所示,經過 VM區域列表, Linux內核爲映射到該特定進程地址空間中的任何具體地址建立實際的頁表項。該場景的後果是每一個進程的頁表均可當作一個 cache子系統。換句話說,若是存在着轉換項, 內核便可使用;若是該轉換項不存在時,則內核能夠基於相應的 VM區域來建立。將頁表看做 cache可提供極大靈活性,由於乾淨頁面的轉換項能夠隨意地刪除;而髒頁面的轉換項只有當該頁面經由文件備份後才能刪除。在刪除以前,須要將頁面內容寫回到文件中,從而清空這些頁面。Linux中頁表這種相似cache的使用行爲提供了一種很是高效的寫時複製(copy-on-write)機制的實現基礎。
VM區域機制的使用示例是若是一個進程將大量的不一樣文件映射到其地址空間中,則該進程(更具體地, Linux內核)可能須要維護一個長達數百項的 VM區域列表。這致使系統運行速度隨着 VM 區域列表的增加而減慢,由於在每次發生頁面錯誤時都須要遍歷該表。爲了減小遍歷該列表的性能影響, Linux操做系統會記錄該列表上的 VM區域數目。在該列表的大小達到特定門限值(一般爲 32項)時, 系統建立另外一個數據結構, 將VM區域組織成自平衡的二叉搜索樹。基於二叉樹搜索算法,能夠經過一系列步驟來定位與虛址相匹配的 VM區域結構。 這些步驟與地址空間中的 VM區域數目存在着對數關係。爲了加快系統訪問全部 VM區域結構的速度, Linux內核同時維護(在到達門限以後)線性列表和二叉樹結構。
四、內核地址空間
整個內核地址空間能夠分解成內核映像空間(也稱爲實體映射段)和內核模塊空間(也稱爲頁表映射段)。
4.1 內核模塊空間
內核模塊空間由內核私有頁表映射,主要用於實現內核的 vmalloc()區域。 這容許系統分配連續的大量虛存範圍。例如,能夠在這個地址空間中分配用於加載特定內核模塊所需的內存。與 vmalloc()相關的地址範圍由兩個與平臺相關的參數 VMALLOC_START和 VMALLOC_ END控制。 vmalloc()區域並不必定佔據整個頁表映射段, 所以有可能將
該內存段的部分空間用於平臺相關的目的和功能。
4.2 內核映像空間
內核映像空間在如下意義上是惟一的:在該內存段中的某個虛址及其所轉換成的物理地址之間存在着直接關聯或映射。這種映射與平臺相關,但一對一的對應關係爲該內存段賦予了名稱。這個內存段能夠經過頁表機制實現,但也可使用與平臺相關的更高效的技術。換句話說, 系統可使用一個相似於(pfn = (addr - PAGE_OFFSET) /PAGE_SIZE)的簡單映射公式。該公式可以最小化徹底基於頁表機制的系統實現的開銷。儘管存在着這種簡單方法,但某些 Linux系統使用了一個稱爲頁幀位圖(page frame map)的表來記錄系統中物理頁幀的狀態。該表對於每一個頁幀都提供一個頁幀描述符(pageframe descriptor, pfd), 其中包含了各類與資源有關的系統維護數據。 這些信息包括正在使用該頁幀的地址空間計數或數量,以及各類指示頁幀是否能換出到磁盤上或者頁面是
否標記爲髒狀態的標誌。
在 Linux中, 物理地址空間的實際大小和虛址空間的大小之間不存在着直接關聯,但其容量都是有限的。爲了更好地管理地址空間, Linux提供了對高端內存的支持。
2、高端內存支持
當代計算機系統上, 虛址空間的大小一般超出物理地址空間的大小。但物理地址空間容量的增加大體符合莫爾定律(Moore's Law), 該定律指出每隔 18個月, 芯片容量將翻倍增加。另外, 虛址空間的大小與平臺相關,所以沒法輕易改變。當物理內存空間接近虛址空間大小時,這個場景對 Linux系統提出了一個特有挑戰:實體映射段的大小可
能不足以映射整個物理地址空間。
high mem接口
諸如 Solaris等操做系統在特定平臺上運行時也面臨着相似挑戰。 Linux系統經過high mem接口來解決這個問題。在 Linux中,高端內存定義爲沒法經過實體映射段加以尋址的物理內存。 highmem接口提供了對該內存空間的間接訪問方法,將高端內存頁面動態映射到一小塊專門保留的內核地址空間(稱爲 kmap()段)。 kmap()接口將頁面參數所指定的頁幀映射到 kmap段。該參數必須是一個指向被映射頁面的頁幀描述符的指針。這個例程會返回該頁面已映射到的虛址。在 kmap段飽和的狀況下,該例程將阻塞,直至出現可用的空間。所以,不能無限期阻塞執行的代碼例如中斷處理程序等將沒法利用高端內存。總而言之, 系統中支持高端內存的話將會產生一些本應儘可能避免的額外開銷,
所以支持高端內存功能是 Linux內核的可選組件。 例如, 該功能在 Linux IA-64系統上是禁用的。
爲了更高效地使用內存, Linux還提供了分頁和交換機制。
3、分頁與交換機制
Linux中的每一個用戶進程都在連續的單一虛址空間上運行。該虛址空間由多種不一樣類型的內存對象組成, 例如程序代碼(只讀)和程序數據(寫時複製)。 當一個程序被加載到某個進程的虛址空間時,該區域由程序可執行代碼完成初始的內存映射和備份。所以虛存系統能夠很是方便地釋放和重用這些文本頁面。例如,當修改一個數據頁面時, 虛存
系統須要建立該頁面的一個私有副本並將其分配給發起該更新操做的進程。私有數據頁面最初稱爲寫時複製(copy-on-write)頁面或按需填零(zero-fill-on-demand)頁面。當出現頁換出狀況時,須要區別對待這些頁面。大多數應用程序都會分配比其在任何特定時刻所用的內存更多的虛存。 例如,程序的文本段常常包含大量不多執行或從不執行的錯誤處
理代碼。爲了不將內存浪費在從未訪問的虛擬頁面上, Linux(以及大多數其餘 UNIX操做系統)使用了按需頁面調度(demand paging)的方法。 在這種方法中, 虛址空間在最初時爲空, 即全部虛擬頁面在頁表中都標記爲不存在(not present)的狀態。 當訪問一個並不存在的虛擬頁面時, CPU會生成頁故障(page fault)。這個錯誤由 Linux內核截獲,從而激活頁故障處理程序。其結果是內核分配一個新的頁幀,肯定被訪問頁面的內容,加載該頁面,最後更新頁表將該頁面標記爲存在狀態。而後執行流程返回到致使該頁面錯誤的進程。因爲所需的頁面此時已存在於是可使用,所以指令繼續執行而不會致使頁故障。
當來自不一樣應用程序的多個線程競爭稀缺資源時,諸如內存之類的物理資源會面臨短缺問題。在這種狀況下,Linux系統須要選擇一個備份了某個最近未被訪問的虛擬頁面的頁幀,而且須要將該頁面寫至磁盤上一個稱爲交換空間的特殊區域。以後系統就能夠重用該頁幀來備份所請求的新虛擬頁面。舊頁面的準確寫入位置與基礎架構中所用的交換空間類型相關。 Linux支持多個交換空間區域,其中每一個區域能夠由整個磁盤分區或者現有文件系統中某個特殊格式的文件組成。所以,須要相應地更新與舊頁面相關聯的頁表。 Linux系統經過將該頁表項標記爲不存在狀態來維護這個更新過程。爲了記錄舊頁面的存儲位置, Linux須要保存該頁面的磁盤位置。概言之,一個標記爲存在狀態的頁表項包含了備份該虛擬頁面的物理頁幀的編號,標記爲不存在狀態的頁表項包含了該頁面的磁盤位置。從某個進程借取一個頁面並將其寫至磁盤子系統的技術稱爲頁面調度(paging)。 與之相關的一種技術稱爲交換(swapping), 這是一種更強大的頁面調度形式,不只能夠借取單個頁面,並且還能借取一個進程的所有頁面集合。 Linux以及多數其餘UNIX操做系統都使用分頁機制而不使用交換機制。
一、替換策略
從執行和穩定性的角度而言,當內存短缺時借取某個特定頁面的關聯性並不重要。另外一方面,從性能的角度來看,要借取哪一個頁面以及什麼時候借取是最重要的。肯定從主存子系統收回哪一個頁面的過程稱爲替換策略。例如,最近最少使用(least recently used, LRU)方法會分析歷史行爲並選取未訪問時間最長的頁面。雖然 LRU能夠做爲一種頁面替換算法來實現,但並不實用。該方法須要在每次訪問主存時都更新某個數據結構,從而會產生極大的開銷。實際中,大多數 UNIX操做系統都採用了各類較低開銷的替換策略的變型, 例如最近未使用(not recently used, NRU)策略。 Linux則採起一種基於 LRU的方法。在 Linux環境中, 內核使用的不可分頁內存的數量可變這個事實進一步複雜化了頁面替換機制。例如,用於存儲文件數據的緩衝區 cache能夠動態地增大或縮小。當內核須要分配一個新的頁幀時, 系統能夠從內核或者某個進程中借取一個頁面。換句話說, 內核不只須要實現一種替換策略,還要實現一種內存平衡策略來肯定可用於內核緩衝區的內存量以及用於備份虛擬頁面的內存量。
二、頁面替換與內存平衡
頁面替換和內存平衡策略的組合是一項沒有清晰且完美解決方案的艱鉅任務。所以,Linux內核使用了多種在實際中可能會工做良好的執行過程。從實現的角度而言, Linux系統中與平臺相關的內核部分在每一個頁表項中添加額外的 2個數據位, 稱爲訪問(access)標誌位和髒(dirty)標誌位。訪問位指示自從最近一次清空該位以來,是否訪問過相應頁面;髒標誌位指示該頁面自從最近一次換入以後是否曾被修改過。 Linux的 kswapd線程按期檢查這兩個數據位。在檢查以後, kswapd清空訪問位。若是 kswapd檢測到內核將要面臨內存不足的情形, 則搶先將最近未用的內存頁換出。若是一個頁面設置了髒標誌位後,則在釋放該頁幀以前,須要將該頁面寫入磁盤。因爲這是一種相對昂貴的操做, kswapd更趨向於釋放已清除了訪問位和髒位(置爲 0)的頁面。根據定義, 這些頁面最近未被訪問過而且在釋放頁幀以前不須要被寫入磁盤,所以能夠較低的性能開銷回收。
各類系統對物理頁面的管理方法不一樣, Linux體系結構中採用了三級頁表的方法。這些頁表能夠實現虛址與物理地址的相互轉換。
4、Linux 頁表
Linux系統在物理內存中爲每一個進程維護一個頁表,並經過實體映射內核段來訪問實際頁表。 Linux中的頁表沒法被換出到交換空間中,這意味着一個分配了大量地址空間的進程有可能會致使內存子系統飽和,由於頁表自己就將耗盡全部可用的內存。相似地,因爲系統裏包含數百個同時活躍着的進程,全部頁表的組合大小也有可能消耗所有可用的內存。當今計算機系統上提供的大型內存子系統使得這種情形不多見,但仍然反映了一個須要解決的容量規劃問題。將頁表保持在物理內存中能夠簡化內核設計,而且無需處理嵌套的頁故障。進程的頁表佈局基於三層樹結構。第一層由全局目錄(global directory,pgd)組成,第二層由中間目錄(middle directory,pmd)組成,第三層由頁表項(page
tableentry, pte)組成。 一般, 每一個目錄結點佔用一個頁幀幷包含固定的項數。 pgd和 pmd 目錄中的各項或者不存在或者指向下一層中的某個目錄。 Pte項表示該樹的葉子結點,包含實際的頁表項。因爲 Linux中的頁表佈局相似於一棵多層的樹,其空間需求與使用中的實際虛址空間成比例。 所以, 該空間需求不是虛址空間的最大容量。 另外, 因爲 Linux將內存做爲一組頁幀來管理,基於固定結點大小的方法並不須要基於線性頁表的系統實現所需的物理連續的大型內存區域 。
當實現虛頁面至物理頁面轉換時, 虛址被分解成多個部分。用於頁表查詢操做的各個虛址部分依賴於 pgd、 pmd和 pte索引(見下圖)。存儲於 mm結構中的頁表指針發起一個查詢操做。該頁表指針引用做爲頁表樹根目錄的全局目錄。 pgd索引所標識的項包含了中間目錄地址。利用該地址與 pmd索引的組合可以定位 pte目錄的地址。對該機制展一個額外層次, 能夠肯定該虛址實際映射到的頁面的 pte。 經過 pte能夠計算出物理頁幀的地址,在利用虛址的偏移量取值便可標識出正確的內存位置。 Linux中的多層頁表實現方法表明了一種與平臺無關的解決方案。若某個特定實現不須要整個樹結構來支持的話,該方案容許將中間目錄轉變成全局目錄。 IA-32環境中能夠採用這種方法,將頁面中間目錄的大小置爲 1。換句話說, IA-32體系結構中將 32位的虛址以下分解: 10位用於頁面目錄, 10位用於頁表項,其他 12位用於偏移量部分。這個地址轉換過程是硬件(內存管理單元(memory management unit, MMU))和軟件(內核)之間協做完成的。 內核與 MMU通訊,爲每一個用戶地址空間標識出映射至物理頁面的虛擬頁面。 MMU可以將該過程當中的任何錯誤條件通知給內核。最多見的錯誤條件與頁故障有關,這時內核須要從輔助存儲器中獲取所需頁面。其餘錯誤條件可能與任何潛在的頁面保護問題有關或由其觸發。從物理地址的角度來看, Linux會區分不一樣的內存存儲區(ZONE_DMA、ZONE_NORMAL和 ZONE_ HIGHMEM),每一個存儲區都具備不一樣功用。大多數的內存分配操做發生在 ZONE_NORMAL區域中, 而 ZONE_HIGHMEM表示大於 896MB的物理地址空間 。