Linux 虛擬記憶體為每個程序提供受保護的虛擬位址空間,同時讓核心共用檔案、超額配置位址空間、回收實體頁面,並將記憶體放置於 NUMA 節點。討論假設存在記憶體管理單元(MMU);沒有 MMU 的系統使用 Linux 另一套 nommu 實作。
轉譯與 TLB #
每次載入、儲存與指令擷取都使用虛擬位址。硬體頁表會將位址轉譯為實體頁框加上偏移。Linux 以五個層級表示階層:
PGD— 頁面全域目錄P4D— 頁面第四層目錄PUD— 頁面上層目錄PMD— 頁面中層目錄PTE— 頁表項目
硬體層級較少的架構會將未使用的 Linux 層級折疊到相鄰層級。CPU 會在轉譯後備緩衝區(TLB)中快取轉譯,因為每次存取都遍歷數個頁表的成本很高。變更對映時,必須使每個能執行受影響位址空間的 CPU 上的過時轉譯失效。
基本頁面大小取決於架構與核心設定。較高頁表層級的對映可以涵蓋較大的頁面,降低頁表記憶體與 TLB 壓力。Linux 提供兩種分開的 huge page 機制:
- HugeTLB 保留或管理明確的 huge page,通常透過
hugetlbfs或MAP_HUGETLB使用;容量與配置失敗屬於其作業契約的一部分。 - Transparent Huge Pages(THP) 依核心政策提升符合條件的對映,不使用獨立的應用程式對映 ABI。提升與壓縮可能引入延遲,因此工作負載應測量,而不是假設一定有益。
位址空間與對映 #
每個使用者空間位址空間由 mm_struct 描述。核心執行緒通常沒有獨立的使用者空間 mm;需要時,架構特定的執行會使用作用中的位址空間上下文。核心以 vm_area_struct(VMA)表示連續的虛擬記憶體區域。VMA 記錄半開區間位址範圍、權限、旗標、支援物件與錯誤處理操作。現代核心使用 maple tree 索引 VMA。
execve(2) 會為可執行檔與共用函式庫區段建立對映。其他常見 VMA 包括堆疊、堆積、匿名 mmap(2) 對映、檔案支援對映、共用記憶體與 vDSO。mmap(2) 保留虛擬範圍及其存取政策;它不必在對映時配置或讀取所有實體頁面。
mprotect(2) 變更 VMA 權限,而 munmap(2) 移除對映。權限檢查同時需要適合的 VMA 與頁表項目。因此,保護頁、不可執行對映與位址空間配置隨機化都是強制執行機制,而不只是記錄。
錯誤、需求分頁與寫入時複製 #
當缺少轉譯,或嘗試存取違反目前項目的權限時,頁面錯誤會將控制權轉移給核心。錯誤處理器找到 VMA,並決定存取是否合法:
- 次要錯誤不需要儲存 I/O 即可解決,例如對映常駐的頁面快取頁,或安裝新配置的零填充頁。
- 主要錯誤需要儲存 I/O 後才能繼續執行。
- 無效位址或不允許的存取通常產生
SIGSEGV;某些物件特定的失敗,例如無法存取檔案支援頁面,則產生SIGBUS。
匿名記憶體最初可以對映共用唯讀零頁。寫入會產生錯誤並取得私有實體頁面。在 fork(2) 後,父程序與子程序通常透過唯讀對映共用頁面。第一個寫入者會產生寫入時複製錯誤,取得私有頁面,並使另一個對映保持不變。寫入時複製讓以 exec() 為目的的子程序執行 fork() 成本低廉,但大型 fork 後的寫入可能成本高昂。
檔案支援對映與 read(2) 使用頁面快取。乾淨的快取頁可以回收,因為核心能重新讀取檔案。髒的共用對映必須寫回,才能重複使用其框。私有檔案對映對寫入使用寫入時複製,因此其修改頁面是匿名頁,而不是對支援檔案的變更。
實體配置與 NUMA #
Linux 使用頁面描述元描述實體框,並依 NUMA 節點與區域分割記憶體。區域編碼硬體限制,例如 DMA 可用記憶體,或適用的 32 位元系統上不由核心永久對映的記憶體。NUMA 節點擁有區域、空閒清單、統計資料與回收狀態,因為存取延遲取決於要求的 CPU 與記憶體銀行。
夥伴配置器提供實體連續的頁面區塊。順序零是一個基本頁面;順序 n 是 2^n 個連續頁面。SLAB 系列配置器(通常是 SLUB)從夥伴配置器取得頁面,並將它們分割成固定大小核心物件的快取。
kmalloc() 會為配置器支援的大小回傳實體連續核心記憶體。vmalloc() 從不連續實體頁面建立虛擬連續核心範圍,因此具有不同的配置成本與裝置 DMA 屬性。驅動程式與 DMA API 應依裝置定址需求選取,而不是彼此替換。
回收、寫回、交換與 OOM #
實體記憶體壓力會導致回收。核心可以:
- 捨棄乾淨的頁面快取頁;
- 寫回髒檔案頁,然後捨棄它們;
- 在交換可用時交換符合條件的匿名頁;以及
- 呼叫 shrinker 回收檔案系統中繼資料等可回收快取。
每個區域都有水位線。跨越低水位線會喚醒 kswapd 進行背景回收。更嚴重壓力下的配置可以進入直接回收,並阻塞到足夠頁面可用。回收政策與多代 LRU 的可用性取決於核心設定與版本;應檢查目標核心,而不是假設存在某種 LRU 實作。
固定頁面、大量核心配置、不可回收的裝置緩衝區,以及受政策保護的頁面,都會降低可回收容量。即使有足夠空閒記憶體,碎片化也可能阻止高階配置。壓縮會遷移可移動頁面,以形成連續空閒區塊;kcompactd 可以在背景執行,配置工作也可以同步執行壓縮。THP 與 DMA 是這類區塊的常見使用者。
當回收與壓縮無法滿足配置時,核心會套用記憶體不足政策。OOM 處理可能是全域的、限定於記憶體 cgroup,或受失敗配置上下文限制。OOM kill 是復原路徑,不代表被選取程序就是記憶體成長的原始來源。在變更 oom_score_adj 前,調查 cgroup 限制、記憶體壓力、配置模式與固定記憶體。
觀察記憶體行為 #
將數個介面一起使用,因為每個介面回報不同層級:
cat /proc/meminfo
cat /proc/vmstat
cat /proc/pressure/memory
cat /proc/$$/maps
cat /proc/$$/smaps_rollup
cat /proc/$$/numa_maps
vmstat 1/proc/PID/maps 列出 VMA。smaps 加入每個 VMA 的 RSS、比例集大小、匿名、檔案、交換與頁面大小統計;smaps_rollup 彙總該資訊。numa_maps 顯示區域性與政策。對 /proc/PID/pagemap 的存取受到限制,因為實體對映資訊可能削弱安全邊界。
對錯誤率而言,程序統計會透過 getrusage(2) 暴露 ru_minflt 與 ru_majflt,而 /proc/PID/stat 包含每個程序的次要與主要錯誤計數器。/proc/vmstat 提供全系統的 pgfault 與 pgmajfault 計數器。將區間內的差異與延遲及 I/O 指標一起解讀:主要錯誤計數會指出需要 I/O 的錯誤路徑,但不能單獨證明儲存裝置造成應用程式延遲。
/proc/pressure/memory 中的壓力停滯資訊(PSI)會回報工作因記憶體壓力而停滯的時間。將 PSI 與 vmstat、cgroup memory.events 及應用程式延遲配對,以區分無害的快取周轉與持續回收或 cgroup 限制壓力。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)