每個(gè)程序員都應(yīng)該了解的“虛擬內(nèi)存”知識(shí)
編輯注:本文是Ulrich Drepper的“每個(gè)程序員應(yīng)該了解的內(nèi)存方面的知識(shí)”文章的第三部分;這一部分談?wù)摿颂摂M內(nèi)存,特別是TLB性能。沒(méi)有閱讀第1部分和第2部分的人可能現(xiàn)在就想讀一讀了。和往常一樣,請(qǐng)將排字錯(cuò)誤報(bào)告之類(lèi)發(fā)送到lwn@lwn.net,而不要發(fā)送到這里的評(píng)論。
4 虛擬內(nèi)存
處理器的虛擬內(nèi)存子系統(tǒng)為每個(gè)進(jìn)程實(shí)現(xiàn)了虛擬地址空間。這讓每個(gè)進(jìn)程認(rèn)為它在系統(tǒng)中是獨(dú)立的。虛擬內(nèi)存的優(yōu)點(diǎn)列表別的地方描述的非常詳細(xì),所以這里就不重復(fù)了。本節(jié)集中在虛擬內(nèi)存的實(shí)際的實(shí)現(xiàn)細(xì)節(jié),和相關(guān)的成本。
虛擬地址空間是由CPU的內(nèi)存管理單元(MMU)實(shí)現(xiàn)的。OS必須填充頁(yè)表數(shù)據(jù)結(jié)構(gòu),但大多數(shù)CPU自己做了剩下的工作。這事實(shí)上是一個(gè)相當(dāng)復(fù)雜的機(jī)制;***的理解它的方法是引入數(shù)據(jù)結(jié)構(gòu)來(lái)描述虛擬地址空間。
由MMU進(jìn)行地址翻譯的輸入地址是虛擬地址。通常對(duì)它的值很少有限制 — 假設(shè)還有一點(diǎn)的話。 虛擬地址在32位系統(tǒng)中是32位的數(shù)值,在64位系統(tǒng)中是64位的數(shù)值。在一些系統(tǒng),例如x86和x86-64,使用的地址實(shí)際上包含了另一個(gè)層次的間接尋址:這些結(jié)構(gòu)使用分段,這些分段只是簡(jiǎn)單的給每個(gè)邏輯地址加上位移。我們可以忽略這一部分的地址產(chǎn)生,它不重要,不是程序員非常關(guān)心的內(nèi)存處理性能方面的東西。{x86的分段限制是與性能相關(guān)的,但那是另一回事了}
4.1 最簡(jiǎn)單的地址轉(zhuǎn)換
有趣的地方在于由虛擬地址到物理地址的轉(zhuǎn)換。MMU可以在逐頁(yè)的基礎(chǔ)上重新映射地址。就像地址緩存排列的時(shí)候,虛擬地址被分割為不同的部分。這些部分被用來(lái)做多個(gè)表的索引,而這些表是被用來(lái)創(chuàng)建最終物理地址用的。最簡(jiǎn)單的模型是只有一級(jí)表。
Figure 4.1: 1-Level Address Translation
圖 4.1 顯示了虛擬地址的不同部分是如何使用的。高字節(jié)部分是用來(lái)選擇一個(gè)頁(yè)目錄的條目;那個(gè)目錄中的每個(gè)地址可以被OS分別設(shè)置。頁(yè)目錄條目決定了物理內(nèi)存頁(yè)的地址;頁(yè)面中可以有不止一個(gè)條目指向同樣的物理地址。完整的內(nèi)存物理地址是由頁(yè)目錄獲得的頁(yè)地址和虛擬地址低字節(jié)部分合并起來(lái)決定的。頁(yè)目錄條目還包含一些附加的頁(yè)面信息,如訪問(wèn)權(quán)限。
頁(yè)目錄的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在內(nèi)存中。OS必須分配連續(xù)的物理內(nèi)存,并將這個(gè)地址范圍的基地址存入一個(gè)特殊的寄存器。然后虛擬地址的適當(dāng)?shù)奈槐挥脕?lái)作為頁(yè)目錄的索引,這個(gè)頁(yè)目錄事實(shí)上是目錄條目的列表。
作為一個(gè)具體的例子,這是 x86機(jī)器4MB分頁(yè)設(shè)計(jì)。虛擬地址的位移部分是22位大小,足以定位一個(gè)4M頁(yè)內(nèi)的每一個(gè)字節(jié)。虛擬地址中剩下的10位指定頁(yè)目錄中1024個(gè)條目的一個(gè)。每個(gè)條目包括一個(gè)10位的4M頁(yè)內(nèi)的基地址,它與位移結(jié)合起來(lái)形成了一個(gè)完整的32位地址。
4.2 多級(jí)頁(yè)表
4MB的頁(yè)不是規(guī)范,它們會(huì)浪費(fèi)很多內(nèi)存,因?yàn)镺S需要執(zhí)行的許多操作需要內(nèi)存頁(yè)的隊(duì)列。對(duì)于4kB的頁(yè)(32位機(jī)器的規(guī)范,甚至通常是64位機(jī)器的規(guī)范),虛擬地址的位移部分只有12位大小。這留下了20位作為頁(yè)目錄的指針。具有220個(gè)條目的表是不實(shí)際的。即使每個(gè)條目只要4比特,這個(gè)表也要4MB大小。由于每個(gè)進(jìn)程可能具有其唯一的頁(yè)目錄,因?yàn)檫@些頁(yè)目錄許多系統(tǒng)中物理內(nèi)存被綁定起來(lái)。
解決辦法是用多級(jí)頁(yè)表。然后這些就能表示一個(gè)稀疏的大的頁(yè)目錄,目錄中一些實(shí)際不用的區(qū)域不需要分配內(nèi)存。因此這種表示更緊湊,使它可能為內(nèi)存中的很多進(jìn)程使用頁(yè)表而并不太影響性能。.
今天最復(fù)雜的頁(yè)表結(jié)構(gòu)由四級(jí)構(gòu)成。圖4.2顯示了這樣一個(gè)實(shí)現(xiàn)的原理圖。
Figure 4.2: 4-Level Address Translation
在這個(gè)例子中,虛擬地址被至少分為五個(gè)部分。其中四個(gè)部分是不同的目錄的索引。被引用的第4級(jí)目錄使用CPU中一個(gè)特殊目的的寄存器。第4級(jí)到第2 級(jí)目錄的內(nèi)容是對(duì)次低一級(jí)目錄的引用。如果一個(gè)目錄條目標(biāo)識(shí)為空,顯然就是不需要指向任何低一級(jí)的目錄。這樣頁(yè)表樹(shù)就能稀疏和緊湊。正如圖4.1,第1級(jí)目錄的條目是一部分物理地址,加上像訪問(wèn)權(quán)限的輔助數(shù)據(jù)。
為了決定相對(duì)于虛擬地址的物理地址,處理器先決定***級(jí)目錄的地址。這個(gè)地址一般保存在一個(gè)寄存器。然后CPU取出虛擬地址中相對(duì)于這個(gè)目錄的索引部分,并用那個(gè)索引選擇合適的條目。這個(gè)條目是下一級(jí)目錄的地址,它由虛擬地址的下一部分索引。處理器繼續(xù)直到它到達(dá)第1級(jí)目錄,那里那個(gè)目錄條目的值就是物理地址的高字節(jié)部分。物理地址在加上虛擬地址中的頁(yè)面位移之后就完整了。這個(gè)過(guò)程稱為頁(yè)面樹(shù)遍歷。一些處理器(像x86和x86-64)在硬件中執(zhí)行這個(gè)操作,其他的需要OS的協(xié)助。
系統(tǒng)中運(yùn)行的每個(gè)進(jìn)程可能需要自己的頁(yè)表樹(shù)。有部分共享樹(shù)的可能,但是這相當(dāng)例外。因此如果頁(yè)表樹(shù)需要的內(nèi)存盡可能小的話將對(duì)性能與可擴(kuò)展性有利。理想的情況是將使用的內(nèi)存緊靠著放在虛擬地址空間;但實(shí)際使用的物理地址不影響。一個(gè)小程序可能只需要第2,3,4級(jí)的一個(gè)目錄和少許第1級(jí)目錄就能應(yīng)付過(guò)去。在一個(gè)采用4kB頁(yè)面和每個(gè)目錄512條目的x86-64機(jī)器上,這允許用4級(jí)目錄對(duì)2MB定位(每一級(jí)一個(gè))。1GB連續(xù)的內(nèi)存可以被第2到第4 級(jí)的一個(gè)目錄和第1級(jí)的512個(gè)目錄定位。
但是,假設(shè)所有內(nèi)存可以被連續(xù)分配是太簡(jiǎn)單了。由于復(fù)雜的原因,大多數(shù)情況下,一個(gè)進(jìn)程的棧與堆的區(qū)域是被分配在地址空間中非常相反的兩端。這樣使得任一個(gè)區(qū)域可以根據(jù)需要盡可能的增長(zhǎng)。這意味著最有可能需要兩個(gè)第2級(jí)目錄和相應(yīng)的更多的低一級(jí)的目錄。
但即使這也不常常匹配現(xiàn)在的實(shí)際。由于安全的原因,一個(gè)可運(yùn)行的(代碼,數(shù)據(jù),堆,棧,動(dòng)態(tài)共享對(duì)象,aka共享庫(kù))不同的部分被映射到隨機(jī)的地址 [未選中的]。隨機(jī)化延伸到不同部分的相對(duì)位置;那意味著一個(gè)進(jìn)程使用的不同的內(nèi)存范圍,遍布于虛擬地址空間。通過(guò)對(duì)隨機(jī)的地址位數(shù)采用一些限定,范圍可以被限制,但在大多數(shù)情況下,這當(dāng)然不會(huì)讓一個(gè)進(jìn)程只用一到兩個(gè)第2和第3級(jí)目錄運(yùn)行。
如果性能真的遠(yuǎn)比安全重要,隨機(jī)化可以被關(guān)閉。OS然后通常是在虛擬內(nèi)存中至少連續(xù)的裝載所有的動(dòng)態(tài)共享對(duì)象(DSO)。
4.3 優(yōu)化頁(yè)表訪問(wèn)
頁(yè)表的所有數(shù)據(jù)結(jié)構(gòu)都保存在主存中;在那里OS建造和更新這些表。當(dāng)一個(gè)進(jìn)程創(chuàng)建或者一個(gè)頁(yè)表變化,CPU將被通知。頁(yè)表被用來(lái)解決每個(gè)虛擬地址到物理地址的轉(zhuǎn)換,用上面描述的頁(yè)表遍歷方式。更多有關(guān)于此:至少每一級(jí)有一個(gè)目錄被用于處理虛擬地址的過(guò)程。這需要至多四次內(nèi)存訪問(wèn)(對(duì)一個(gè)運(yùn)行中的進(jìn)程的單次訪問(wèn)來(lái)說(shuō)),這很慢。有可能像普通數(shù)據(jù)一樣處理這些目錄表?xiàng)l目,并將他們緩存在L1d,L2等等,但這仍然非常慢。
從虛擬內(nèi)存的早期階段開(kāi)始,CPU的設(shè)計(jì)者采用了一種不同的優(yōu)化。簡(jiǎn)單的計(jì)算顯示,只有將目錄表?xiàng)l目保存在L1d和更高級(jí)的緩存,才會(huì)導(dǎo)致可怕的性能問(wèn)題。每個(gè)絕對(duì)地址的計(jì)算,都需要相對(duì)于頁(yè)表深度的大量的L1d訪問(wèn)。這些訪問(wèn)不能并行,因?yàn)樗鼈円蕾囉谇懊娌樵兊慕Y(jié)果。在一個(gè)四級(jí)頁(yè)表的機(jī)器上,這種單線性將 至少至少需要12次循環(huán)。再加上L1d的非命中的可能性,結(jié)果是指令流水線沒(méi)有什么能隱藏的。額外的L1d訪問(wèn)也消耗了珍貴的緩存帶寬。
所以,替代于只是緩存目錄表?xiàng)l目,物理頁(yè)地址的完整的計(jì)算結(jié)果被緩存了。因?yàn)橥瑯拥脑?,代碼和數(shù)據(jù)緩存也工作起來(lái),這樣的地址計(jì)算結(jié)果的緩存是高效的。由于虛擬地址的頁(yè)面位移部分在物理頁(yè)地址的計(jì)算中不起任何作用,只有虛擬地址的剩余部分被用作緩存的標(biāo)簽。根據(jù)頁(yè)面大小這意味著成百上千的指令或數(shù)據(jù)對(duì)象共享同一個(gè)標(biāo)簽,因此也共享同一個(gè)物理地址前綴。
保存計(jì)算數(shù)值的緩存叫做旁路轉(zhuǎn)換緩存(TLB)。因?yàn)樗仨毞浅5目?,通常這是一個(gè)小的緩存?,F(xiàn)代CPU像其它緩存一樣,提供了多級(jí)TLB緩存;越高級(jí)的緩存越大越慢。小號(hào)的L1級(jí)TLB通常被用來(lái)做全相聯(lián)映像緩存,采用LRU回收策略。最近這種緩存大小變大了,而且在處理器中變得集相聯(lián)。其結(jié)果之一就是,當(dāng)一個(gè)新的條目必須被添加的時(shí)候,可能不是最久的條目被回收于替換了。
正如上面提到的,用來(lái)訪問(wèn)TLB的標(biāo)簽是虛擬地址的一個(gè)部分。如果標(biāo)簽在緩存中有匹配,最終的物理地址將被計(jì)算出來(lái),通過(guò)將來(lái)自虛擬地址的頁(yè)面位移地址加到緩存值的方式。這是一個(gè)非??斓倪^(guò)程;也必須這樣,因?yàn)槊織l使用絕對(duì)地址的指令都需要物理地址,還有在一些情況下,因?yàn)槭褂梦锢淼刂纷鳛殛P(guān)鍵字的 L2查找。如果TLB查詢未命中,處理器就必須執(zhí)行一次頁(yè)表遍歷;這可能代價(jià)非常大。
通過(guò)軟件或硬件預(yù)取代碼或數(shù)據(jù),會(huì)在地址位于另一頁(yè)面時(shí),暗中預(yù)取TLB的條目。硬件預(yù)取不可能允許這樣,因?yàn)橛布?huì)初始化非法的頁(yè)面表遍歷。因此程序員不能依賴硬件預(yù)取機(jī)制來(lái)預(yù)取TLB條目。它必須使用預(yù)取指令明確的完成。就像數(shù)據(jù)和指令緩存,TLB可以表現(xiàn)為多個(gè)等級(jí)。正如數(shù)據(jù)緩存,TLB通常表現(xiàn)為兩種形式:指令TLB(ITLB)和數(shù)據(jù)TLB(DTLB)。高級(jí)的TLB像L2TLB通常是統(tǒng)一的,就像其他的緩存情形一樣。
#p#
4.3.1 使用TLB的注意事項(xiàng)
TLB是以處理器為核心的全局資源。所有運(yùn)行于處理器的線程與進(jìn)程使用同一個(gè)TLB。由于虛擬到物理地址的轉(zhuǎn)換依賴于安裝的是哪一種頁(yè)表樹(shù),如果頁(yè)表變化了,CPU不能盲目的重復(fù)使用緩存的條目。每個(gè)進(jìn)程有一個(gè)不同的頁(yè)表樹(shù)(不算在同一個(gè)進(jìn)程中的線程),內(nèi)核與內(nèi)存管理器VMM(管理程序)也一樣,如果存在的話。也有可能一個(gè)進(jìn)程的地址空間布局發(fā)生變化。有兩種解決這個(gè)問(wèn)題的辦法:
- 當(dāng)頁(yè)表樹(shù)變化時(shí)TLB刷新。
- TLB條目的標(biāo)簽附加擴(kuò)展并唯一標(biāo)識(shí)其涉及的頁(yè)表樹(shù)
***種情況,只要執(zhí)行一個(gè)上下文切換TLB就被刷新。因?yàn)榇蠖鄶?shù)OS中,從一個(gè)線程/進(jìn)程到另一個(gè)的切換需要執(zhí)行一些核心代碼,TLB刷新被限制進(jìn)入或離開(kāi)核心地址空間。在虛擬化的系統(tǒng)中,當(dāng)內(nèi)核必須調(diào)用內(nèi)存管理器VMM和返回的時(shí)候,這也會(huì)發(fā)生。如果內(nèi)核和/或內(nèi)存管理器沒(méi)有使用虛擬地址,或者當(dāng)進(jìn)程或內(nèi)核調(diào)用系統(tǒng)/內(nèi)存管理器時(shí),能重復(fù)使用同一個(gè)虛擬地址,TLB必須被刷新。當(dāng)離開(kāi)內(nèi)核或內(nèi)存管理器時(shí),處理器繼續(xù)執(zhí)行一個(gè)不同的進(jìn)程或內(nèi)核。
刷新TLB高效但昂貴。例如,當(dāng)執(zhí)行一個(gè)系統(tǒng)調(diào)用,觸及的內(nèi)核代碼可能僅限于幾千條指令,或許少許新頁(yè)面(或一個(gè)大的頁(yè)面,像某些結(jié)構(gòu)的Linux 的就是這樣)。這個(gè)工作將替換觸及頁(yè)面的所有TLB條目。對(duì)Intel帶128ITLB和256DTLB條目的Core2架構(gòu),完全的刷新意味著多于 100和200條目(分別的)將被不必要的刷新。當(dāng)系統(tǒng)調(diào)用返回同一個(gè)進(jìn)程,所有那些被刷新的TLB條目可能被再次用到,但它們沒(méi)有了。內(nèi)核或內(nèi)存管理器常用的代碼也一樣。每條進(jìn)入內(nèi)核的條目上,TLB必須擦去再裝,即使內(nèi)核與內(nèi)存管理器的頁(yè)表通常不會(huì)改變。因此理論上說(shuō),TLB條目可以被保持一個(gè)很長(zhǎng)時(shí)間。這也解釋了為什么現(xiàn)在處理器中的TLB緩存都不大:程序很有可能不會(huì)執(zhí)行時(shí)間長(zhǎng)到裝滿所有這些條目。
當(dāng)然事實(shí)逃脫不了CPU的結(jié)構(gòu)。對(duì)緩存刷新優(yōu)化的一個(gè)可能的方法是單獨(dú)的使TLB條目失效。例如,如果內(nèi)核代碼與數(shù)據(jù)落于一個(gè)特定的地址范圍,只有落入這個(gè)地址范圍的頁(yè)面必須被清除出TLB。這只需要比較標(biāo)簽,因此不是很昂貴。在部分地址空間改變的場(chǎng)合,例如對(duì)去除內(nèi)存頁(yè)的一次調(diào)用,這個(gè)方法也是有用的,
更好的解決方法是為T(mén)LB訪問(wèn)擴(kuò)展標(biāo)簽。如果除了虛擬地址的一部分之外,一個(gè)唯一的對(duì)應(yīng)每個(gè)頁(yè)表樹(shù)的標(biāo)識(shí)(如一個(gè)進(jìn)程的地址空間)被添加,TLB將根本不需要完全刷新。內(nèi)核,內(nèi)存管理程序,和獨(dú)立的進(jìn)程都可以有唯一的標(biāo)識(shí)。這種場(chǎng)景唯一的問(wèn)題在于,TLB標(biāo)簽可以獲得的位數(shù)異常有限,但是地址空間的位數(shù)卻不是。這意味著一些標(biāo)識(shí)的再利用是有必要的。這種情況發(fā)生時(shí)TLB必須部分刷新(如果可能的話)。所有帶有再利用標(biāo)識(shí)的條目必須被刷新,但是希望這是一個(gè)非常小的集合。
當(dāng)多個(gè)進(jìn)程運(yùn)行在系統(tǒng)中時(shí),這種擴(kuò)展的TLB標(biāo)簽具有一般優(yōu)勢(shì)。如果每個(gè)可運(yùn)行進(jìn)程對(duì)內(nèi)存的使用(因此TLB條目的使用)做限制,進(jìn)程最近使用的TLB條目,當(dāng)其再次列入計(jì)劃時(shí),有很大機(jī)會(huì)仍然在TLB。但還有兩個(gè)額外的優(yōu)勢(shì):
- 特殊的地址空間,像內(nèi)核和內(nèi)存管理器使用的那些,經(jīng)常僅僅進(jìn)入一小段時(shí)間;之后控制經(jīng)常返回初始化此次調(diào)用的地址空間。沒(méi)有標(biāo)簽,就有兩次TLB 刷新操作。有標(biāo)簽,調(diào)用地址空間緩存的轉(zhuǎn)換地址將被保存,而且由于內(nèi)核與內(nèi)存管理器地址空間根本不會(huì)經(jīng)常改變TLB條目,系統(tǒng)調(diào)用之前的地址轉(zhuǎn)換等等可以仍然使用。
- 當(dāng)同一個(gè)進(jìn)程的兩個(gè)線程之間切換時(shí),TLB刷新根本就不需要。雖然沒(méi)有擴(kuò)展TLB標(biāo)簽時(shí),進(jìn)入內(nèi)核的條目會(huì)破壞***個(gè)線程的TLB的條目。
有些處理器在一些時(shí)候?qū)崿F(xiàn)了這些擴(kuò)展標(biāo)簽。AMD給帕西菲卡(Pacifica)虛擬化擴(kuò)展引入了一個(gè)1位的擴(kuò)展標(biāo)簽。在虛擬化的上下文中,這個(gè)1 位的地址空間ID(ASID)被用來(lái)從客戶域區(qū)別出內(nèi)存管理程序的地址空間。這使得OS能夠避免在每次進(jìn)入內(nèi)存管理程序的時(shí)候(例如為了處理一個(gè)頁(yè)面錯(cuò)誤)刷新客戶的TLB條目,或者當(dāng)控制回到客戶時(shí)刷新內(nèi)存管理程序的TLB條目。這個(gè)架構(gòu)未來(lái)會(huì)允許使用更多的位。其它主流處理器很可能會(huì)隨之適應(yīng)并支持這個(gè)功能。
4.3.2 影響TLB性能
有一些因素會(huì)影響TLB性能。***個(gè)是頁(yè)面的大小。顯然頁(yè)面越大,裝進(jìn)去的指令或數(shù)據(jù)對(duì)象就越多。所以較大的頁(yè)面大小減少了所需的地址轉(zhuǎn)換總次數(shù),即需要更少的TLB緩存條目。大多數(shù)架構(gòu)允許使用多個(gè)不同的頁(yè)面尺寸;一些尺寸可以并存使用。例如,x86/x86-64處理器有一個(gè)普通的4kB的頁(yè)面尺寸,但它們也可以分別用4MB和2MB頁(yè)面。IA-64 和 PowerPC允許如64kB的尺寸作為基本的頁(yè)面尺寸。
然而,大頁(yè)面尺寸的使用也隨之帶來(lái)了一些問(wèn)題。用作大頁(yè)面的內(nèi)存范圍必須是在物理內(nèi)存中連續(xù)的。如果物理內(nèi)存管理的單元大小升至虛擬內(nèi)存頁(yè)面的大小,浪費(fèi)的內(nèi)存數(shù)量將會(huì)增長(zhǎng)。各種內(nèi)存操作(如加載可執(zhí)行文件)需要頁(yè)面邊界對(duì)齊。這意味著平均每次映射浪費(fèi)了物理內(nèi)存中頁(yè)面大小的一半。這種浪費(fèi)很容易累加;因此它給物理內(nèi)存分配的合理單元大小劃定了一個(gè)上限。
在x86-64結(jié)構(gòu)中增加單元大小到2MB來(lái)適應(yīng)大頁(yè)面當(dāng)然是不實(shí)際的。這是一個(gè)太大的尺寸。但這轉(zhuǎn)而意味著每個(gè)大頁(yè)面必須由許多小一些的頁(yè)面組成。這些小頁(yè)面必須在物理內(nèi)存中連續(xù)。以4kB單元頁(yè)面大小分配2MB連續(xù)的物理內(nèi)存具有挑戰(zhàn)性。它需要找到有512個(gè)連續(xù)頁(yè)面的空閑區(qū)域。在系統(tǒng)運(yùn)行一段時(shí)間并且物理內(nèi)存開(kāi)始碎片化以后,這可能極為困難(或者不可能)
因此在Linux中有必要在系統(tǒng)啟動(dòng)的時(shí)候,用特別的Huge TLBfs文件系統(tǒng),預(yù)分配這些大頁(yè)面。一個(gè)固定數(shù)目的物理頁(yè)面被保留,以單獨(dú)用作大的虛擬頁(yè)面。這使可能不會(huì)經(jīng)常用到的資源捆綁留下來(lái)。它也是一個(gè)有限的池;增大它一般意味著要重啟系統(tǒng)。盡管如此,大頁(yè)面是進(jìn)入某些局面的方法,在這些局面中性能具有保險(xiǎn)性,資源豐富,而且麻煩的安裝不會(huì)成為大的妨礙。數(shù)據(jù)庫(kù)服務(wù)器就是一個(gè)例子。
增大最小的虛擬頁(yè)面大小(正如選擇大頁(yè)面的相反面)也有它的問(wèn)題。內(nèi)存映射操作(例如加載應(yīng)用)必須確認(rèn)這些頁(yè)面大小。不可能有更小的映射。對(duì)大多數(shù)架構(gòu)來(lái)說(shuō),一個(gè)可執(zhí)行程序的各個(gè)部分位置有一個(gè)固定的關(guān)系。如果頁(yè)面大小增加到超過(guò)了可執(zhí)行程序或DSO(Dynamic Shared Object)創(chuàng)建時(shí)考慮的大小,加載操作將無(wú)法執(zhí)行。腦海里記得這個(gè)限制很重要。圖4.3顯示了一個(gè)ELF二進(jìn)制的對(duì)齊需求是如何決定的。它編碼在 ELF程序頭部。
- $ eu-readelf -l /bin/ls
- Program Headers:
- Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
- ...
- LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0132ac 0x0132ac R E 0x200000
- LOAD 0x0132b0 0x00000000006132b0 0x00000000006132b0 0x001a71 0x001a71 RW 0x200000
- ...
Figure 4.3: ELF 程序頭表明了對(duì)齊需求
在這個(gè)例子中,一個(gè)x86-64二進(jìn)制,它的值為0×200000 = 2,097,152 = 2MB,符合處理器支持的***頁(yè)面尺寸。
使用較大內(nèi)存尺寸有第二個(gè)影響:頁(yè)表樹(shù)的級(jí)數(shù)減少了。由于虛擬地址相對(duì)于頁(yè)面位移的部分增加了,需要用來(lái)在頁(yè)目錄中使用的位,就沒(méi)有剩下許多了。這意味著當(dāng)一個(gè)TLB未命中時(shí),需要做的工作數(shù)量減少了。
超出使用大頁(yè)面大小,它有可能減少移動(dòng)數(shù)據(jù)時(shí)需要同時(shí)使用的TLB條目數(shù)目,減少到數(shù)頁(yè)。這與一些上面我們談?wù)摰木彺媸褂玫膬?yōu)化機(jī)制類(lèi)似。只有現(xiàn)在對(duì)齊需求是巨大的??紤]到TLB條目數(shù)目如此小,這可能是一個(gè)重要的優(yōu)化。
4.4 虛擬化的影響
OS映像的虛擬化將變得越來(lái)越流行;這意味著另一個(gè)層次的內(nèi)存處理被加入了想象。進(jìn)程(基本的隔間)或者OS容器的虛擬化,因?yàn)橹簧婕耙粋€(gè)OS而沒(méi)有落入此分類(lèi)。類(lèi)似Xen或KVM的技術(shù)使OS映像能夠獨(dú)立運(yùn)行 — 有或者沒(méi)有處理器的協(xié)助。這些情形下,有一個(gè)單獨(dú)的軟件直接控制物理內(nèi)存的訪問(wèn)。
圖 4.4: Xen 虛擬化模型
對(duì)Xen來(lái)說(shuō)(見(jiàn)圖4.4),Xen VMM(Xen內(nèi)存管理程序)就是那個(gè)軟件。但是,VMM沒(méi)有自己實(shí)現(xiàn)許多硬件的控制,不像其他早先的系統(tǒng)(包括Xen VMM的***個(gè)版本)的VMM,內(nèi)存以外的硬件和處理器由享有特權(quán)的Dom0域控制?,F(xiàn)在,這基本上與沒(méi)有特權(quán)的DomU內(nèi)核一樣,就內(nèi)存處理方面而言,它們沒(méi)有什么不同。這里重要的是,VMM自己分發(fā)物理內(nèi)存給Dom0和DomU內(nèi)核,然后就像他們是直接運(yùn)行在一個(gè)處理器上一樣,實(shí)現(xiàn)通常的內(nèi)存處理
為了實(shí)現(xiàn)完成虛擬化所需的各個(gè)域之間的分隔,Dom0和DomU內(nèi)核中的內(nèi)存處理不具有無(wú)限制的物理內(nèi)存訪問(wèn)權(quán)限。VMM不是通過(guò)分發(fā)獨(dú)立的物理頁(yè)并讓客戶OS處理地址的方式來(lái)分發(fā)內(nèi)存;這不能提供對(duì)錯(cuò)誤或欺詐客戶域的防范。替代的,VMM為每一個(gè)客戶域創(chuàng)建它自己的頁(yè)表樹(shù),并且用這些數(shù)據(jù)結(jié)構(gòu)分發(fā)內(nèi)存。好處是對(duì)頁(yè)表樹(shù)管理信息的訪問(wèn)能得到控制。如果代碼沒(méi)有合適的特權(quán),它不能做任何事。 在虛擬化的Xen支持中,這種訪問(wèn)控制已被開(kāi)發(fā),不管使用的是參數(shù)的或硬件的(又名全)虛擬化??蛻粲蛞砸鈭D上與參數(shù)的和硬件的虛擬化極為相似的方法,給每個(gè)進(jìn)程創(chuàng)建它們的頁(yè)表樹(shù)。每當(dāng)客戶OS修改了VMM調(diào)用的頁(yè)表,VMM就會(huì)用客戶域中更新的信息去更新自己的影子頁(yè)表。這些是實(shí)際由硬件使用的頁(yè)表。顯然這個(gè)過(guò)程非常昂貴:每次對(duì)頁(yè)表樹(shù)的修改都需要VMM的一次調(diào)用。而沒(méi)有虛擬化時(shí)內(nèi)存映射的改變也不便宜,它們現(xiàn)在變得甚至更昂貴。 考慮到從客戶OS的變化到VMM以及返回,其本身已經(jīng)相當(dāng)昂貴,額外的代價(jià)可能真的很大。這就是為什么處理器開(kāi)始具有避免創(chuàng)建影子頁(yè)表的額外功能。這樣很好不僅是因?yàn)樗俣鹊膯?wèn)題,而且它減少了VMM消耗的內(nèi)存。Intel有擴(kuò)展頁(yè)表(EPTs),AMD稱之為嵌套頁(yè)表(NPTs)?;旧蟽煞N技術(shù)都具有客戶OS的頁(yè)表,來(lái)產(chǎn)生虛擬的物理地址。然后通過(guò)每個(gè)域一個(gè)EPT/NPT樹(shù)的方式,這些地址會(huì)被進(jìn)一步轉(zhuǎn)換為真實(shí)的物理地址。這使得可以用幾乎非虛擬化情境的速度進(jìn)行內(nèi)存處理,因?yàn)榇蠖鄶?shù)用來(lái)內(nèi)存處理的VMM條目被移走了。它也減少了VMM使用的內(nèi)存,因?yàn)楝F(xiàn)在一個(gè)域(相對(duì)于進(jìn)程)只有一個(gè)頁(yè)表樹(shù)需要維護(hù)。 額外的地址轉(zhuǎn)換步驟的結(jié)果也存儲(chǔ)于TLB。那意味著TLB不存儲(chǔ)虛擬物理地址,而替代以完整的查詢結(jié)果。已經(jīng)解釋過(guò)AMD的帕西菲卡擴(kuò)展為了避免TLB刷新而給每個(gè)條目引入ASID。ASID的位數(shù)在最初版本的處理器擴(kuò)展中是一位;這正好足夠區(qū)分VMM和客戶OS。Intel有服務(wù)同一個(gè)目的的虛擬處理器 ID(VPIDs),它們只有更多位。但對(duì)每個(gè)客戶域VPID是固定的,因此它不能標(biāo)記單獨(dú)的進(jìn)程,也不能避免TLB在那個(gè)級(jí)別刷新。
對(duì)虛擬OS,每個(gè)地址空間的修改需要的工作量是一個(gè)問(wèn)題。但是還有另一個(gè)內(nèi)在的基于VMM虛擬化的問(wèn)題:沒(méi)有什么辦法處理兩層的內(nèi)存。但內(nèi)存處理很難(特別是考慮到像NUMA一樣的復(fù)雜性,見(jiàn)第5部分)。Xen方法使用一個(gè)單獨(dú)的VMM,這使***的(或***的)處理變得困難,因?yàn)樗袃?nèi)存管理實(shí)現(xiàn)的復(fù)雜性,包括像發(fā)現(xiàn)內(nèi)存范圍之類(lèi)“瑣碎的”事情,必須被復(fù)制于VMM。OS有完全成熟的與***的實(shí)現(xiàn);人們確實(shí)想避免復(fù)制它們。
圖 4.5: KVM 虛擬化模型
這就是為什么對(duì)VMM/Dom0模型的分析是這么有吸引力的一個(gè)選擇。圖4.5顯示了KVM的Linux內(nèi)核擴(kuò)展如何嘗試解決這個(gè)問(wèn)題的。并沒(méi)有直接運(yùn)行在硬件之上且管理所有客戶的單獨(dú)的VMM,替代的,一個(gè)普通的Linux內(nèi)核接管了這個(gè)功能。這意味著Linux內(nèi)核中完整且復(fù)雜的內(nèi)存管理功能,被用來(lái)管理系統(tǒng)的內(nèi)存??蛻粲蜻\(yùn)行于普通的用戶級(jí)進(jìn)程,創(chuàng)建者稱其為“客戶模式”。虛擬化的功能,參數(shù)的或全虛擬化的,被另一個(gè)用戶級(jí)進(jìn)程KVM VMM控制。這也就是另一個(gè)進(jìn)程用特別的內(nèi)核實(shí)現(xiàn)的KVM設(shè)備,去恰巧控制一個(gè)客戶域。
這個(gè)模型相較Xen獨(dú)立的VMM模型好處在于,即使客戶OS使用時(shí),仍然有兩個(gè)內(nèi)存處理程序在工作,只需要在Linux內(nèi)核里有一個(gè)實(shí)現(xiàn)。不需要像 Xen VMM那樣從另一段代碼復(fù)制同樣的功能。這帶來(lái)更少的工作,更少的bug,或許還有更少的兩個(gè)內(nèi)存處理程序接觸產(chǎn)生的摩擦,因?yàn)橐粋€(gè)Linux客戶的內(nèi)存處理程序與運(yùn)行于裸硬件之上的Linux內(nèi)核的外部?jī)?nèi)存處理程序,做出了相同的假設(shè)。
總的來(lái)說(shuō),程序員必須清醒認(rèn)識(shí)到,采用虛擬化時(shí),內(nèi)存操作的代價(jià)比沒(méi)有虛擬化要高很多。任何減少這個(gè)工作的優(yōu)化,將在虛擬化環(huán)境付出更多。隨著時(shí)間的過(guò)去,處理器的設(shè)計(jì)者將通過(guò)像EPT和NPT技術(shù)越來(lái)越減少這個(gè)差距,但它永遠(yuǎn)都不會(huì)完全消失。
英文原文:Memory part 3: Virtual Memory
譯文鏈接:http://www.oschina.net/translate/what-every-programmer-should-know-about-virtual-memory-part3