概念概述¶
Linux 中的記憶體管理是一個複雜的系統,它在多年演進中包含了越來越多的功能,以支援從無 MMU 微控制器到超級計算機的各種系統。沒有 MMU 的系統的記憶體管理被稱為 nommu,它無疑值得一份專門的文件,這份文件有望最終完成。然而,儘管有些概念是相同的,但在這裡我們假設 MMU 可用,並且 CPU 可以將虛擬地址轉換為物理地址。
虛擬記憶體入門¶
計算機系統中的物理記憶體是一種有限的資源,即使對於支援記憶體熱插拔的系統,可安裝的記憶體量也有硬性限制。物理記憶體不一定是連續的;它可能作為一組不同的地址範圍進行訪問。此外,不同的 CPU 架構,甚至同一架構的不同實現,對這些地址範圍的定義也有不同的看法。
所有這些使得直接處理物理記憶體變得相當複雜,為了避免這種複雜性,虛擬記憶體的概念應運而生。
虛擬記憶體將物理記憶體的細節從應用程式軟體中抽象出來,允許只將所需資訊儲存在物理記憶體中(按需分頁),並提供了一種機制來保護和控制程序之間的資料共享。
透過虛擬記憶體,每一個記憶體訪問都使用一個虛擬地址。當 CPU 解碼一條從(或向)系統記憶體讀取(或寫入)的指令時,它會將該指令中編碼的虛擬地址轉換為記憶體控制器可以理解的物理地址。
物理系統記憶體被劃分為頁幀或頁。每個頁的大小是架構特定的。有些架構允許從幾個支援的值中選擇頁大小;這種選擇在核心構建時透過設定相應的核心配置選項來執行。
每個物理記憶體頁可以對映為一個或多個虛擬頁。這些對映由頁表描述,頁表允許將程式使用的虛擬地址轉換為物理記憶體地址。頁表是分層組織的。
層級最低的頁表包含軟體使用的實際頁的物理地址。較高層級的頁表包含屬於較低層級的頁的物理地址。指向頂層頁表的指標位於一個暫存器中。當 CPU 執行地址轉換時,它使用該暫存器訪問頂層頁表。虛擬地址的高位用於索引頂層頁表中的一個條目。然後,該條目用於訪問層級中的下一級,虛擬地址的下一位作為該層級頁表的索引。虛擬地址的最低位定義了實際頁內的偏移量。
大頁¶
地址轉換需要多次記憶體訪問,而記憶體訪問相對於 CPU 速度來說是緩慢的。為了避免將寶貴的處理器週期浪費在地址轉換上,CPU 維護著一個此類轉換的快取,稱為轉換後備緩衝區(或 TLB)。通常,TLB 是一個相當稀缺的資源,具有大記憶體工作集的應用程式會因為 TLB 未命中而經歷效能下降。
許多現代 CPU 架構允許頁表中的更高級別直接對映記憶體頁。例如,在 x86 上,可以使用二級和三級頁表中的條目對映 2M 甚至 1G 的頁。在 Linux 中,這些頁被稱為大頁。使用大頁顯著減少了 TLB 的壓力,提高了 TLB 命中率,從而改善了整體系統效能。
Linux 中有兩種機制支援使用大頁對映物理記憶體。第一種是HugeTLB 檔案系統,或稱 hugetlbfs。它是一個偽檔案系統,使用 RAM 作為其後端儲存。在此檔案系統中建立的檔案資料駐留在記憶體中並使用大頁進行對映。hugetlbfs 在 HugeTLB 頁 中有描述。
另一種更近期的啟用大頁的機制稱為透明大頁,或稱 THP。與 hugetlbfs 要求使用者和/或系統管理員配置系統記憶體的哪些部分應且可以被大頁對映不同,THP 對使用者透明地管理此類對映,因此得名。有關 THP 的更多詳細資訊,請參閱 透明大頁支援。
區域¶
硬體通常對不同物理記憶體範圍的訪問方式施加限制。在某些情況下,裝置無法對所有可定址記憶體執行 DMA。在另一些情況下,物理記憶體的大小超出了虛擬記憶體的最大可定址大小,需要特殊操作來訪問部分記憶體。Linux 根據其可能的用途將記憶體頁分組為區域。例如,ZONE_DMA 將包含可供裝置用於 DMA 的記憶體,ZONE_HIGHMEM 將包含未永久對映到核心地址空間的記憶體,而 ZONE_NORMAL 將包含正常定址的頁。
記憶體區域的實際佈局是硬體相關的,因為並非所有架構都定義所有區域,並且不同平臺對 DMA 的要求也不同。
節點¶
許多多處理器機器是 NUMA(非統一記憶體訪問)系統。在此類系統中,記憶體被組織成多個儲存體,根據與處理器的“距離”不同,這些儲存體具有不同的訪問延遲。每個儲存體被稱為一個節點,對於每個節點,Linux 構建一個獨立的記憶體管理子系統。一個節點有其自己的區域集、空閒和已用頁列表以及各種統計計數器。您可以在 什麼是 NUMA?` 和 NUMA 記憶體策略 中找到有關 NUMA 的更多詳細資訊。
頁快取¶
物理記憶體是易失的,將資料讀入記憶體的常見情況是從檔案中讀取。每當讀取檔案時,資料都會放入頁快取中,以避免後續讀取時昂貴的磁碟訪問。同樣,當寫入檔案時,資料會放置在頁快取中,並最終進入後端儲存裝置。寫入的頁被標記為髒,當 Linux 決定將它們用於其他目的時,它會確保裝置上的檔案內容與更新後的資料同步。
匿名記憶體¶
匿名記憶體或匿名對映表示沒有檔案系統支援的記憶體。此類對映是為程式的棧和堆隱式建立的,或透過顯式呼叫 mmap(2) 系統呼叫建立的。通常,匿名對映只定義程式被允許訪問的虛擬記憶體區域。讀訪問將導致建立一個頁表條目,該條目引用一個填充零的特殊物理頁。當程式執行寫操作時,將分配一個常規物理頁來儲存寫入的資料。該頁將被標記為髒,如果核心決定重新利用它,髒頁將被交換出去。
回收¶
在系統生命週期中,一個物理頁可以用於儲存不同型別的資料。它可以是核心內部資料結構、供裝置驅動程式使用的 DMA 緩衝區、從檔案系統讀取的資料、使用者空間程序分配的記憶體等。
根據頁的使用情況,Linux 記憶體管理對其區別對待。那些可以隨時釋放的頁,無論是由於它們快取了其他地方(例如硬碟)可用的資料,還是因為它們可以被交換出去(再次到硬碟),都被稱為可回收的。最顯著的可回收頁類別是頁快取和匿名記憶體。
在大多數情況下,持有核心內部資料並用作 DMA 緩衝區的頁無法被重新利用,它們會一直固定在那裡,直到其使用者釋放。此類頁被稱為不可回收的。然而,在某些情況下,即使是佔用核心資料結構的頁也可以被回收。例如,檔案系統元資料的記憶體中快取可以從儲存裝置重新讀取,因此當系統面臨記憶體壓力時,可以將它們從主記憶體中丟棄。
釋放可回收物理記憶體頁並重新利用它們的過程被稱為(令人驚訝的是!)回收。Linux 可以根據系統狀態非同步或同步地回收頁。當系統未載入時,大部分記憶體是空閒的,分配請求將立即從空閒頁供應中得到滿足。隨著負載增加,空閒頁的數量減少,當達到某個閾值(低水位線)時,分配請求將喚醒 kswapd 守護程序。它將非同步掃描記憶體頁,如果其中包含的資料在其他地方可用,則直接釋放它們,否則將其驅逐到後端儲存裝置(還記得那些髒頁嗎?)。隨著記憶體使用量進一步增加並達到另一個閾值——最小水位線——分配將觸發直接回收。在這種情況下,分配會暫停,直到回收足夠的記憶體頁來滿足請求。
記憶體規整¶
隨著系統的執行,任務會分配和釋放記憶體,導致記憶體碎片化。儘管使用虛擬記憶體可以將分散的物理頁呈現為虛擬連續的範圍,但有時仍需要分配大的物理連續記憶體區域。例如,當裝置驅動程式需要一個大的 DMA 緩衝區,或者 THP 分配一個大頁時,可能會出現這種需求。記憶體規整解決了碎片化問題。這種機制將已佔用的頁從記憶體區域的下部移動到區域上部的空閒頁。當規整掃描完成後,空閒頁會在區域的開頭聚集在一起,從而使大物理連續區域的分配成為可能。
與回收類似,記憶體規整可能在 kcompactd 守護程序中非同步發生,或者作為記憶體分配請求的結果同步發生。
OOM 殺手¶
在負載較高的機器上,記憶體可能會耗盡,核心將無法回收足夠的記憶體以繼續執行。為了挽救系統的其餘部分,它會呼叫OOM 殺手。
OOM 殺手會選擇一個任務進行犧牲,以維護整體系統健康。被選中的任務會被終止,希望能在此任務退出後釋放足夠的記憶體以恢復正常執行。