天天看點

從資源池和管理的角度了解實體記憶體

早就想搞一下記憶體問題了!這次正趁着搞bigmemory核心,可以寫一篇文章了。本文旨在記錄,不包含細節,細節的話,google,百度均可,很多人已經寫了不少了。我隻是按照自己的了解記錄一下記憶體的點點滴滴而已,沒有一家之言,不讨論,不較真。

最簡單的模型是馮.諾依曼提出的原始模型,簡單的把資料和指令存放在記憶體中,然後機器從記憶體中取出指令和資料進行計算,如下圖所示:

<a href="http://blog.51cto.com/attachment/201311/183358204.jpg" target="_blank"></a>

當時的機器是為了執行一個特定的任務,但是這種存儲執行模型作為的一個最簡單的核心,為後代的逐漸複雜化奠定了基礎。我的觀點還是,一個概念或者其它什麼東西,之是以複雜是因為它經得起複雜。羅馬帝國始終脫離不了城邦格局,它經不起複雜,它崩潰了!

一台機器執行一個任務,太浪費資源了,略過中間的掙紮,直接到了分時系統時代,一台機器可以執行多個任務了,如果切換機制足夠好,這些任務可以滿足低延遲需求。馮諾依曼機器的核心是處理器和記憶體,處理器沿時間軸推進,記憶體則空間平面上展開,分時系統在時間軸上分割了多個任務,它需要在空間平面上同樣分割多個任務,于是記憶體變成了一種共享的資源,如何在多個任務之間配置設定這個單一的共享的記憶體資源成了後來技術發展的重軸戲。

段式記憶體模型将一個程式劃分為不同的段,不同任務的不同段處在記憶體的不同段當中,如下圖所示:

<a href="http://blog.51cto.com/attachment/201311/183413899.jpg" target="_blank"></a>

但是,這種模型有兩個顯而易見的缺點,第一,很難滿足一個大記憶體需求的任務,第二,一分就是一個段,段必須處在一個連續的空間。

一個任務所需要的記憶體大小以及位置不應該依賴其它任務的記憶體的大小和位置,并且記憶體的位置也不應該是永久性的,任務使用記憶體就應該和人們使用公共廁所一樣。程式任務隻管自己的計算邏輯,用到記憶體的時候,不必自己操心,應該有一個服務機構為其現場配置設定記憶體,配置設定多少算好呢,答案就是就可能少,按照基本機關配置設定,也就是說隻配置設定程式現在用的那個記憶體,即便說馬上就要用另一塊記憶體,那也要等到時候再說,這樣就做到了公平和高效!既滿足了盡可能多的程式的記憶體需求,又不會浪費任何不會用到的記憶體。

記憶體頁面的概念被提出後,頁就成了配置設定記憶體的最小機關,而MMU則成了為程式配置設定記憶體的服務管理機構,有了這個新機構,應用程式再也不用考慮實體記憶體的位置的大小以及偏移問題了。

虛拟記憶體的提出是革命性的,在以前,程式不得不維護自己段寄存器,以明确自己所需記憶體的位置,隻要是有一個位址,就可以根據段寄存器知道它位于記憶體中的什麼地方,也就是說,那個時候,程式是直接使用實體記憶體的。虛拟記憶體出現後,MMU接管了記憶體管理的一切,應用程式不必關心記憶體的位置和大小了。如果是32位系統,那麼程式被承諾可以使用高達4G的記憶體,如果是64位系統,...至于自己使用的記憶體在什麼位置,則不必關心,可用的4G記憶體隻是許諾,等到需要的時候MMU自然會給你,如果沒有空閑記憶體,自然會給你個說法。MMU作為一個仲裁和管理機構,前提是大家必須信任它!

如果說初期的直接獨占使用實體記憶體是王政時代, 段式管理是貴族寡頭時代的話,虛拟記憶體管理則真正到了民主時代,各項機構有條不紊運作。

頁式模型顧名思義就是以頁面為基礎進行記憶體管理,注意,最終的記憶體頁面并不直接和程式打交道,它通過MMU和程式打交道。由于有了MMU這個中間層,它負責将一個程式的虛拟記憶體位址映射到實際的實體位址,怎麼做到的呢?當然是通過一張表,即頁表來查詢的。

<a href="http://blog.51cto.com/attachment/201311/183428588.jpg" target="_blank"></a>

由于采用了MMU這個中間層,實體記憶體不再和程式直接打交道,則實體記憶體的形式就變得不再重要,它可以是記憶體條,也可以是磁盤,甚至可以是裝置,隻要MMU能給出合理的解釋,并且按照應用程式通路記憶體的規則來通路這些實體并能給出正确的結果即可。這就使得檔案映射,裝置映射成了可能。如下圖所示:

<a href="http://blog.51cto.com/attachment/201311/183450968.jpg" target="_blank"></a>

雖然說“從虛拟記憶體映射到實體記憶體通過查表可以實作”,但是具體的查表過程卻非常複雜,并不是簡單的一對一的映射這麼實作的。實際的實作是通過一個多極頁表的方式實作的。所謂的多級頁表是将虛拟位址分為不同的部分,每一部分代表不同的索引。這樣就可以按照記憶體的範圍進行區域劃分,更好的進行頁表的管理。具體的方法和圖示無須google,百度即可!

在實際的實作中,為何要使用多級頁表而不是單級頁表呢?這是從管理成本來考慮的。由于是一個表,那麼它便有連續記憶體存儲的需求,這樣才好根據索引來快速定位。如果是單層頁表,那麼即使一個頁面被配置設定,也需要建立整個頁表,32位的情況下以4K頁面為例,需要20位要尋址頁面基位址,20位的話單級頁表需要一下子建立4M大小的頁表。

使用多級頁表并不是為了減少記憶體使用,說實話,如果把所有的4G映射都建立頁表項的話,采用兩極頁表還會浪費頁目錄表占用的4K空間,然而并不能如此考慮問題,記憶體使用分布是不遵循幂率的,是以你不必考慮黑天鵝事件。大部分情況下,不會建立太多的頁表,即使建立1000個頁表,它也會多數承載于連續的頁目錄項中,很多的頁表是不需要配置設定記憶體的。主旨就是,将管理結構分級往前推,往前推,往前推!

本文最後會給出一個程式,讓你眼見為實地明白頁表到底占據多少空間以及記憶體的分布如何影響頁表占據記憶體空間的大小。

有點懵了,怎麼現在才開始說換入換出,是不是順序弄亂了,不是說很早的UNIX時代就有換入換出了麼?如今Linux還保留着swap程序(其實是核心線程,因為總有人較真,說什麼核心線程不能叫做程序,看書看多了)這個名稱。非也,不是弄錯了,而是我想基于交換機制來談一下虛拟記憶體的意義,并不是講換入換出機制本身。

這是最實際不過的了,分時系統将一個程序拉到前台來運作的時候,将該程序的映像從磁盤換到記憶體,同時将正在執行的程序換出到磁盤。雖然簡單,但是卻沒有後續的可擴充性。這其實是基于最初的記憶體模型修正的,而絲毫沒有用到虛拟記憶體的優勢。虛拟記憶體不關心程序,不關心記憶體頁面的位置,切斷了實體記憶體和程序的關系,隻關注頁面本身,頁面的内容可以來自計算,來自檔案,來自裝置,...

事實上,整體的換入換出模型更加适合虛拟化,所謂的虛拟化指的是全部的,包括CPU在内的虛拟化。它不适合虛拟記憶體,既然CPU都是分時處理不同程序的,記憶體為何就不能分頁面映射給不同的程序呢?

由整體換入換出的弊端引起的直接結果就是頁面的換入換出。一個頁面就是一個頁面,它不和程序進行關聯,隻和頁表項進行關聯,這是虛拟記憶體管理高效性和公平性之根本。單獨頁面的管理以及基于頁面的換入換出機制,虛拟記憶體之根本!

在使用虛拟記憶體之後,實體記憶體的角色已經不再像早期那樣重要,它退化成了一個資源的角色,作為一種資源,原則上它可以是無限大的,而且越大越好,但是受制于以下的因素:

a.管理成本:是資源就要有效管理,而管理本身也是消耗資源的,它在各個系統上都有一個上限。

b.體系結構的相容性考慮:如果說從一張白紙重新作畫,那再簡單不過了,然而現實并非如此,總線寬度為了相容性并不能随意擴充。

c.實作成本:計算機上的任何概念都是一個有限集,更加明确的原則就是,它不要求100%的好,而是要求90%可用即可。

d.電梯效應:超高層的摩天大樓不可能建造,并不是因為底層承載不了上層的持續壓力,而是如果建成了高層大廈,建得越高,電梯占據的空間就越大,達到閥值後,電梯的空間将超越使用空間。這是管理成本的另一層含義,隻是更加嚴重些!

即使不能使實體記憶體無限大,也可以讓它更大,PAE就是這個想法的産物。

實體記憶體并非一定要和虛拟記憶體的大小一緻,如果是這樣的話,虛拟記憶體的意義是不明顯的。以32位系統為例,N個程序均被許諾有4G記憶體,然而它們共享4G實體記憶體,誰也不能同時用盡所有記憶體,然而如果真的有這樣需求的程序怎麼辦?那隻能頻繁的換入換出了,雖然也是可以實作,但是更好的做法就是安裝N*4G的記憶體,可是這樣的話好像又退回到了段式管理,隻是記憶體的實際位置不再确定,隻是将基于段的管理改成了粒度更細的頁式管理而已。由于N的不确定性以及記憶體使用的不确定性,沒有必要安裝那麼大的記憶體,最終的方案就是安裝稍微大一些的記憶體,比如16G,32G,64G的記憶體,機器就足以飛起來了!

到此為止,可能你還是不明白32位的系統如何去識别4G以上的記憶體。注意,MMU使用頁表來定位頁面的位置,隻要頁表項能填入一個大于32位的數字,就能尋址到4G以上的記憶體--因為32位可以尋址4G(why?...),加上硬體位址總線寬度能超越32位,那就能定位到大于4G的記憶體,定位到這個頁面後,将其映射回32位的某個位址即可。如下圖所示:

<a href="http://blog.51cto.com/attachment/201311/183507622.jpg" target="_blank"></a>

記住,實體記憶體僅僅就是一個資源的角色,在不考慮相容性以及管理成本的情況下,當然越大越好,并非非要和虛拟位址空間一緻。在32位系統中,程序的虛拟位址空間永遠都是32位也就是4G的,但是這4G空間的位址卻是可以映射到任意的實體位址空間,一切盡在MMU,說白了就是,第一,頁表項的映射訓示到了任意的實體頁面,第二就是位址總線的寬度允許尋址到那個位置(否則,雖然從程式上講不會出錯,但是在位址總線上發射位址的時候會發生回繞!)。程式員可以照着書上的例子寫出代碼,但是沒有什麼書教你在哪些機器上這些代碼可以得到你預期的結果!!

上面說的加大實體記憶體供應的說法,其實有一種實作那就是PAE。PAE是什麼,google吧,如果怕google動不動就RESET,那麼百度也能得到結果!PAE允許你尋址36位的實體位址空間!也就是說允許你安裝64G的記憶體。按照實體記憶體隻是資源池的概念,所有的32位程序共享所有安裝的實體記憶體,每一個程序尋址32位虛拟位址,通過MMU實際可以通路36位的實體位址。

是時候說一下Linux了,對于實踐者和懷疑論者以及書生乃至抑郁症患者抑或精神病而言,沒有任何高談闊論可以比得上一個實際的例子了。

Linux采用了一種極其簡單的位址映射方式,那就是将核心空間的代碼以及資料和實際的使用者程序隔離開來,怎麼個簡單法呢?很簡單,那就是将程序的位址空間劃分為使用者态的3G和核心态的1G,所謂的使用者态就是非特權态,對于那些愛看書的優秀學生而言,他們熟悉的語言是第3特權護環,不管怎麼說,反正就是程序可見的資料和代碼的位址空間!使用者态的位址空間映射,核心不過問,而核心态的映射,所有的使用者态共享,作為一個管理機構,它是唯一的,如下圖所示:

<a href="http://blog.51cto.com/attachment/201311/183543539.jpg" target="_blank"></a>

使用者态可以有自己的映射,核心态的映射全部交給了核心本身!核心簡化了,它可以用一種更加簡單且高效的方式實作管理,那就是一一線性映射,也就是将一個連續的核心位址空間,映射到一塊連續的實體位址空間,雖然最終的訪存還是需要MMU,但是起碼不需要做複雜的管理工作了。映射到哪塊連續的實體位址空間好呢?當然是最初的空間好,因為核心的映射位置不能依賴實體位址空間的大小。

以上就是一一線性映射的由來!但是為了滿足動态的核心态服務的記憶體需求,比如動态插拔的核心子產品,比如使用者系統調用的臨時需求,核心的虛拟位址空間還要留下一部分用來映射這些動态的資料。最終核心态的虛拟位址空間的布局成了如下布局:

<a href="http://blog.51cto.com/attachment/201311/183557811.jpg" target="_blank"></a>

這樣的布局本身沒有什麼問題,特别是如果你了解Windows的自映射以及核心分頁機制之後,你就會發現Linux的方式是多麼的原生态,多麼的環保。然而這種方式有一個疑問-現如今還不能成為問題:

為了保持一一映射的關系,所有的一一映射的記憶體必須獨占且常駐記憶體,和最原始的馮諾依曼機器實作那種方式一樣,是以,Linux的一一映射方式真正回歸了原生态,隻是中間有一個MMU例行公事而已!

現在考慮PAE的模式!我讨厭《尼羅河上的慘案》中那個穿西服的家夥那種方式!如果啟動了PAE,意味着系統中存在大量的記憶體頁面,為了管理這些頁面,Linux核心必須為這些頁面建立結構體,即struct page。Linux系統是按照夥伴系統配置設定page的,夥伴系統要求事先必須存在page的索引,是以必須要考慮page結構體們占據的記憶體空間。作為基礎管理資料結構,這些page結構都處在核心的一一映射位址空間,然而一一映射的位址空間大小是有限制的,即896M!按照一個page結構體32位元組大小來計算的話,你算一下能允許多少page被索引,記住,896M不能全都用于page結構,記憶體管理隻是Linux核心的一部分而已,另外還有大頭戲,程序管理!結果就是在現行的Linux記憶體管理模式下,隻能管理有限的page,是以你并不能安裝64G的記憶體在Linux系統上。

但是,作為一個通用且前衛的系統,關鍵是Linus動不動就動粗口的情形下,Linux有自己的解決方案,其中不外乎以下兩點:

a.使用大頁面:一般而言,一個頁面4K,這樣為了索引大記憶體就需要大量的頁面,但是如果一個頁面4M或者2M的話,索引大量記憶體就不需要大量的頁面了,頁面數量減少了,頁面管理結構所占據的記憶體空間也就減少了!

b.使用獨立的4G/4G模式:雖然大頁面可以緩解管理結構占據記憶體太大的問題,但是并不能解決!Linux支援一種4G/4G模式,即不再将所有程序的最上面1G的核心空間共享,而是每個程序的使用者态獨占4G虛拟位址空間,切換到核心态時,同時切換到另一個4G位址空間,即獨立的4G的核心位址空間!核心态不再借用使用者程序位址空間,而是獨立出來一個4G空間來尋址,在X86上,意味着不管是系統調用,不管是中斷還是異常,陷入核心時,都要切換CR3寄存器,這意味着你要付出一些代價!切換CR3的代價是昂貴的,它不光是save/restore的代價,更是取消了cache加速的代價!

說白了,Linux核心的管理成本太昂貴,很多的管理資料結構都要占據核心的一一線性映射的896M的空間!雖然程序結構task_struct結構體不大,但是896M除以sizeof(struct task_struct)的話,也不是一個很大的數,再加上CR3指向的頁目錄頁表等,896M真的容不下什麼太多的内容!最根本的限制那就是這種機制限制了系統同時運作程序的數量!但是,Linux核心的這種原始記憶體映射的優點也顯而易見,那就是社群大牛們總是可以設計出一些精巧的資料結構,往往在惡劣環境下寫出的代碼,一定是高效的代碼,這同時也是微軟的信條!

是時候展示一個簡單的代碼了。該代碼讓你看一下管理成本,雖然很簡單,但是可以讓你看到一個程序本身雖然不占據什麼記憶體空間,但是光其頁表就占據大量的記憶體。在展示代碼之前,先看一個Linux核心的編譯宏HIGHPTE,該宏決定你能不能将頁表這種吃記憶體的管理結構配置設定在高端記憶體,即大于896M的實體記憶體,也就是說如果你啟用了該宏,就不必在一一線性映射區域配置設定頁表!我的系統啟用了該宏,意味着頁表都盡量配置設定在高端記憶體。以下是代碼:

執行前,檢視/proc/meminfo後,由于頁表配置設定在高端記憶體,即896上的記憶體(我的系統2000M記憶體),其内容為:

HighTotal: 1628104 kB

HighFree: 1261560 kB

執行後,total數目為768,符合預期,因為上1G的記憶體屬于核心空間,不能mmap。檢視/proc/meminfo後,HIGH記憶體減小,減小多少自己算

HighFree: 1255360 kB

如果不用FIX參數,那麼頁表項同樣也是建立那麼多,在我測試下來,還多了很多,1024個頁表項全部建立成功,但是,HIGH記憶體占據反而減少了:

HighFree: 1257344 kB

這說明記憶體配置設定的布局,也就是頁表有與否,會影響管理成本!什麼是High記憶體,是大于896M的所有實體記憶體!可用通過/proc/meminfo看出來!

到底安裝多少實體記憶體算多,可以算出來嗎?IT技術算精确技術嗎?我不覺得喜歡較真的人能徹底了解TCP。衛星技術要比IT技術高深TMD的多了,怎麼沒有人能預測出歐洲衛星殘骸墜落地球的具體地點,追究TCP重傳具體時間具體算法的人可能要徹夜計算衛星殘骸墜落地點了,由于精神高度緊張,猝死的可能性比猜中50%的可能性更高!

實際上,很多技術都是基于機率的,都是追求90%的可用而不是100%的完美!為何大家不必為衛星殘骸墜落地球而擔心,因為地球上70%都是海洋,陸地上人類聚集的地點不足1%,是以砸中人的機率字計算吧。如果我被砸中了,算是我對較真的神詛咒的一種報應吧,但是并不絕對!

曾經,大家不約而同地使用64位系統,實際上沒有誰的系統可以用到大記憶體,就算吃記憶體的遊戲,32位也已經足夠,關鍵的是能申請到實體記憶體。隻要實體記憶體大即可,虛拟記憶體有誰會用到那麼大呢?你更多的受益于多核而不是64位。我們對時間的感覺要比對空間的感覺敏感得多。

按照局部性原理,一個CPU在同一時刻隻能處理有限區域的記憶體資料!時間比空間更重要,強勁的CPU要比64位的虛拟位址空間更加有用,虛拟位址空間并不是實際落實的實體地記憶體空間,效率和速度展現在落實的實體記憶體上!即使是實體記憶體的因素,安裝比較大的實體記憶體更多的是在于減少換入換出開銷,而不是為了滿足同時通路大量記憶體的需求。即使有同時需要大量記憶體的程式,也可以通過并發處理,通過多核來将其平坦化!

我并不诋毀64位,隻是覺得按照資源池的概念,你隻需要在乎實體記憶體,而無需在乎虛拟記憶體!别提資料庫,我讨厭資料庫,因為它總是為自己占據大量磁盤需要很高的CPU處理能力而狂呼資源不夠,但是實際上早期的ER模型并不适合現在的大資料處理!是資料庫本身的問題,而不是處理資源不夠!

與其64位,不如來個8核,已經有了8核,32位夠了!

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1322581