程序為何不越界,內存管理單元MMU全解析
在 Linux 操作系統的復雜架構中,內存管理堪稱核心樞紐,而內存管理單元(MMU)則是其中當之無愧的關鍵角色。它雖深藏幕后,卻如同一位技藝精湛的指揮家,有條不紊地掌控著內存世界的 “乾坤”,確保系統穩定、高效地運行。
當我們在Linux系統中同時開啟多個應用程序,或是運行復雜的計算任務時,MMU 便悄然施展其強大的能力。它在虛擬內存與物理內存之間搭建起一座無形卻精準的橋梁,將應用程序使用的虛擬地址,絲毫不差地轉換為實際的物理內存地址。這一過程,就像是在龐大的圖書館中,快速準確地找到所需書籍的位置。由此,每個進程仿佛擁有了一片專屬的內存天地,彼此獨立運行,互不干擾,極大地提升了系統的多任務處理能力。
不僅如此,MMU 還肩負著內存訪問權限的 “安?!?重任。它嚴格審視每一次內存訪問請求,只有合法的訪問才能順利通過,有效杜絕了非法操作對系統內存的破壞,為整個 Linux 系統的穩定運行筑牢堅實防線。接下來,讓我們一同深入 Linux 內存管理的神秘領域,探尋 MMU 究竟如何施展魔法,掌控內存乾坤 。
一、內存管理單元簡介
1.1MMU概述
MMU是Memory Management Unit的縮寫,中文名是內存管理單元,有時稱作分頁內存管理單元(英語:paged memory management unit,縮寫為PMMU)。它是一種負責處理中央處理器(CPU)的內存訪問請求的計算機硬件。它的功能包括虛擬地址到物理地址的轉換(即虛擬內存管理)、內存保護、中央處理器高速緩存的控制,在較為簡單的計算機體系結構中,負責總線的仲裁以及存儲體切換(bank switching,尤其是在8位的系統上)。
內存管理單元(MMU)的一個重要功能是使系統能夠運行多個任務,作為獨立程序在自己的私有虛擬內存空間中運行。它們不需要了解系統的物理內存映射,即硬件實際使用的地址,也不需要了解可能同時執行的其他程序。
圖片
打個比方,我們可以把計算機的內存想象成一個大型的倉庫,里面存放著各種各樣的物資(數據和程序)。而運行在計算機上的眾多程序,就如同一個個前來領取物資的客戶。如果沒有一個有效的管理機制,這些客戶可能會在倉庫里隨意翻找,不僅效率低下,還可能會出現混亂,導致物資的損壞或丟失。
而 MMU 就像是這個倉庫的大管家,它制定了一套嚴格而有序的管理規則。每個客戶(程序)在訪問倉庫(內存)時,都需要通過 MMU 這個大管家進行 “登記” 和 “授權”,然后由大管家將客戶的 “需求指令”(虛擬地址)準確無誤地轉換為倉庫中實際的 “物資存放位置”(物理地址),這樣客戶就能順利地獲取到自己需要的物資,同時也保證了倉庫的秩序和物資的安全。
從專業的角度來說,MMU 是一種負責處理中央處理器(CPU)的內存訪問請求的計算機硬件。它的出現,讓計算機系統能夠更加高效、穩定地運行多個任務,仿佛為每個任務都打造了一個屬于它們自己的獨立小世界,互不干擾,各自精彩。
1.2MMU起源
在計算機發展的早期階段,硬件資源十分有限,就像是一個狹小的倉庫,內存空間非常小,而且程序對內存的訪問是直接而簡單粗暴的,就如同在一個小房間里隨意堆放物品,沒有任何管理規則。程序員需要手動分配和釋放內存,這就要求他們對內存的使用有深入的了解,稍有不慎就可能出現內存泄漏或其他錯誤,就像在小房間里找東西時,不小心把東西放錯地方或者弄丟了一樣。那個時候,程序直接訪問物理內存,操作系統也只是簡單地 “加載”“運行” 或 “卸載” 應用程序。
隨著計算機技術的飛速發展和軟件的不斷膨脹,計算機需要處理的任務越來越復雜,內存需求也越來越大。就好比一個小倉庫要容納越來越多的物資,單任務批處理的模式已經無法滿足需求,多任務處理的需求應運而生。同時,應用程序所需的內存量也不斷增加,甚至超過了物理內存的大小。這就像小倉庫已經裝不下所有的物資了,怎么辦呢?
為了解決這些問題,聰明的計算機科學家們提出了虛擬內存的思想。虛擬內存就像是給計算機內存這個小倉庫加了一個 “虛擬擴展空間”,程序所需的內存可以遠超物理內存的大小,操作系統會把當前需要執行的部分留在內存中,而不需要執行的部分留在磁盤中,就像把暫時不用的物資存放到倉庫外面的臨時存儲區。這樣,就可以滿足多個應用程序同時駐留內存并并發執行,就好像在小倉庫有限的空間里,通過合理調配物資,讓多個客戶都能順利拿到自己需要的東西。
在這樣的背景下,MMU 應運而生,它就像是專門為管理這個復雜的內存 “倉庫” 而聘請的高級大管家。MMU 接替了操作系統內存管理中比較復雜的部分,比如地址翻譯,將虛擬地址翻譯成物理地址,就像大管家能夠準確地把客戶的 “虛擬需求位置” 轉換為實際的 “物資存放位置”。同時,內存訪問效率則交給了 cache(高速緩存)去做,或者通過提高內存總線的帶寬來實現,就像給倉庫配備了快速通道,讓物資的搬運更加高效。
二、MMU的核心功能
2.1虛擬地址與物理地址的轉換魔法
在計算機的內存世界里,存在著兩種重要的地址概念:虛擬地址和物理地址。虛擬地址是程序在運行時所使用的地址,就像是我們在地圖上規劃的一條虛擬路線,它并不直接對應實際的物理內存位置。而物理地址則是內存芯片上實實在在的地址,是數據真正存儲的地方,就如同地圖上的實際地點。
那么,MMU 是如何將虛擬地址轉換為物理地址的呢?這就要借助頁表這個神奇的工具了。頁表就像是一本詳細的地址翻譯字典,記錄著虛擬地址和物理地址之間的映射關系。當 CPU 發出一個虛擬地址請求時,MMU 就會像查字典一樣,在頁表中查找對應的物理地址。
具體來說,虛擬地址會被劃分成頁號和頁內偏移兩部分。頁號就像是字典的索引,通過它可以快速定位到頁表中對應的條目,而這個條目里就存儲著對應的物理頁號。然后,將物理頁號和頁內偏移組合起來,就得到了最終的物理地址,從而能夠準確地訪問到內存中的數據。
舉個例子,假設我們有一個程序要訪問虛擬地址 0x12345678。MMU 首先會提取出頁號,比如是 0x1234,然后在頁表中查找這個頁號對應的條目。假設找到的條目顯示對應的物理頁號是 0x5678,而頁內偏移是 0x9ABC,那么最終的物理地址就是 0x56789ABC。通過這樣的轉換,程序就能夠順利地訪問到它所需要的數據,就好像我們通過地圖上的路線規劃找到了實際的目的地一樣。
mmu開啟以后會有以下特點:
- 多個程序獨立運行
- 虛擬地址是連續的(物理內存可以有碎片)
- 允許操作系統管理內存
下圖顯示的系統說明了內存的虛擬和物理視圖。單個系統中的不同處理器和設備可能具有不同的虛擬地址映射和物理地址映射。操作系統編寫程序,使MMU在這兩個內存視圖之間進行轉換:
圖片
要做到這一點,虛擬內存系統中的硬件必須提供地址轉換,即將處理器發出的虛擬地址轉換為主內存中的物理地址。MMU使用虛擬地址中最重要的位來索引轉換表中的條目,并確定正在訪問哪個塊。MMU將代碼和數據的虛擬地址轉換為實際系統中的物理地址。該轉換將在硬件中自動執行,并且對應用程序是透明的。除了地址轉換之外,MMU還可以控制每個內存區域的內存訪問權限、內存順序和緩存策略。
MMU對執行的任務或應用程序可以不了解系統的物理內存映射,也可以不了解同時運行的其他程序。每個程序可以使用相同的虛擬內存地址空間。即使物理內存是碎片化的,還可以使用一個連續的虛擬內存映射。此虛擬地址空間與系統中內存的實際物理映射分開的。應用程序被編寫、編譯和鏈接,以在虛擬內存空間中運行。
圖片
如上圖所示,TLB是MMU中最近訪問的頁面翻譯的緩存。對于處理器執行的每個內存訪問,MMU將檢查轉換是否緩存在TLB中。如果所請求的地址轉換在TLB中導致命中,則該地址的翻譯立即可用。TLB本質是一塊高速緩存。數據cache緩存地址(虛擬地址或者物理地址)和數據。TLB緩存虛擬地址和其映射的物理地址。TLB根據虛擬地址查找cache,它沒得選,只能根據虛擬地址查找。所以TLB是一個虛擬高速緩存。
每個TLB entry通常不僅包含物理地址和虛擬地址,還包含諸如內存類型、緩存策略、訪問權限、地址空間ID(ASID)和虛擬機ID(VMID)等屬性。如果TLB不包含處理器發出的虛擬地址的有效轉換,稱為TLB Miss,則將執行外部轉換頁表查找。MMU內的專用硬件使它能夠讀取內存中的轉換表。
然后,如果翻譯頁表沒有導致頁面故障,則可以將新加載的翻譯緩存在TLB中,以便進行后續的重用。簡單概括一下就是:硬件存在TLB后,虛擬地址到物理地址的轉換過程發生了變化。虛擬地址首先發往TLB確認是否命中cache,如果cache hit直接可以得到物理地址。否則,一級一級查找頁表獲取物理地址。并將虛擬地址和物理地址的映射關系緩存到TLB中。
如果操作系統修改了可能已經緩存在TLB中的轉換的entry,那么操作系統就有責任使這些未更新的TLB entry invaild。當執行A64代碼時,有一個TLBI,它是一個TLB無效的指令:
TLBI <type><level>{IS} {, <Xt>}
TLB可以保存固定數量的entry。可以通過由轉換頁表遍歷引起的外部內存訪問次數和獲得高TLB命中率來獲得最佳性能。ARMv8-A體系結構提供了一個被稱為連續塊entry的特性,以有效地利用TLB空間。轉換表每個entry都包含一個連續的位。當設置時,這個位向TLB發出信號,表明它可以緩存一個覆蓋多個塊轉換的單個entry。查找可以索引到連續塊所覆蓋的地址范圍中的任何位置。因此,TLB可以為已定義的地址范圍緩存一個entry從而可以在TLB中存儲更大范圍的虛擬地址。
2.2內存保護的堅固防線
MMU 不僅是地址轉換的高手,還是內存保護的堅固防線。在多任務的操作系統環境下,多個進程同時運行,就像一個熱鬧的大社區里住著許多戶人家。如果沒有有效的管理,這些進程可能會互相干擾,就像鄰居之間隨意闖入對方的房子一樣。
MMU 通過硬件機制實現了內存訪問授權,為每個進程分配了獨立的地址空間,就像給每個家庭都分配了獨立的房子,互不干擾。當一個進程試圖訪問內存時,MMU 會檢查該進程是否有權限訪問目標內存區域。只有當進程具有相應的訪問權限時,MMU 才會允許這次訪問,否則就會阻止訪問,并向操作系統報告,就像保安會阻止沒有權限的人進入特定區域一樣。
例如,一個進程 A 正在運行,它只能訪問屬于自己地址空間內的內存。如果進程 A 試圖訪問進程 B 的內存區域,MMU 會立即發現這個非法訪問行為,并阻止它,從而保證了系統的穩定性和安全性。這種內存保護機制有效地防止了進程之間的非法訪問,避免了因一個進程的錯誤而導致整個系統崩潰的情況,就像堅固的圍墻保護著每個家庭的安全,讓整個社區能夠和諧穩定地運轉。
三、地址轉換全解析
3.1分頁機制的奧秘
分頁機制是 Linux 內存管理的核心機制之一,它就像是一個巧妙的拼圖游戲,將虛擬內存和物理內存都劃分成固定大小的塊,這些塊就被稱為頁面(page)和頁幀(page frame) 。
對于虛擬地址空間而言,它被劃分成一個個大小相等的單元,每個單元就是一頁,我們用 VPN(Virtual Page Number,虛擬頁面號)來標識每一頁。而在物理地址空間,同樣被分為固定大小的單元,每個單元就是一個頁幀,用 PFN(Physical Frame Number,物理頁幀號)來標識。
分頁管理內存的核心,就是建立起虛擬地址頁到物理地址頁幀的映射關系。這就好比制作一份詳細的地圖,地圖上標記著每個虛擬頁面(VPN)應該對應到哪個物理頁幀(PFN),而這份 “地圖” 就是頁表。通過頁表,MMU 能夠快速準確地找到虛擬地址對應的物理地址,實現數據的高效訪問。
例如,在一個 32 位的系統中,假設頁面大小為 4KB(2 的 12 次方字節),那么虛擬地址空間(4GB,即 2 的 32 次方字節)就可以被劃分為 2 的 20 次方個頁面。每個頁面都有一個唯一的 VPN,從 0 開始編號。當程序訪問一個虛擬地址時,MMU 會根據這個地址計算出對應的 VPN,然后在頁表中查找該 VPN 對應的 PFN,從而找到數據所在的物理頁幀。
3.2地址轉換的詳細步驟
當處理器發出64位虛擬地址進行指令獲取或數據訪問時,MMU硬件將虛擬地址轉換為相應的物理地址。對于一個虛擬地址,前16位[63:47]必須全部為0或1,否則該地址將引發故障。通過使用低位來給出選定部分內的偏移量,以便MMU將來自塊表entry的物理地址位與來自原始地址的低位組合起來,以生成最終地址。
當 CPU 發出一個虛擬地址請求時,MMU 就開始了它緊張而有序的工作,將虛擬地址轉換為物理地址,這個過程就像是一場精密的接力賽,每一步都至關重要。
- 虛擬地址的拆分:虛擬地址首先會被拆分成兩部分,一部分是虛擬頁面號(VPN),另一部分是虛擬地址偏移(VA offset)。VPN 就像是一個房間號,用于確定虛擬地址所在的頁面;而 VA offset 則是房間內的具體位置,即頁內偏移。
- 頁表的查找:MMU 根據計算得到的 VPN,在頁表中查找對應的物理頁幀號(PFN)。頁表就像是一本地址字典,存儲著 VPN 和 PFN 的映射關系。MMU 會以 VPN 作為索引,在頁表中快速定位到對應的條目,從而獲取到 PFN。
- 物理地址的生成:得到 PFN 后,MMU 將 PFN 和虛擬地址偏移(VA offset)組合起來,就得到了最終的物理地址。因為頁幀的大小和頁面大小相同,所以頁內偏移在虛擬地址和物理地址中是一致的。通過這種方式,MMU 成功地將虛擬地址轉換為物理地址,使得 CPU 能夠準確地訪問內存中的數據。
例如,假設虛擬地址為 0x12345678,頁面大小為 4KB。首先,將虛擬地址拆分為 VPN 和 VA offset,VPN = 0x12345,VA offset = 0x678。然后,MMU 在頁表中查找 VPN = 0x12345 對應的 PFN,假設找到的 PFN 為 0x56789。最后,將 PFN 和 VA offset 組合起來,得到物理地址為 0x56789678。這樣,CPU 就可以通過這個物理地址訪問到內存中的數據了。
3.3多級頁表的優化策略
在早期的計算機系統中,使用的是一級頁表,它就像是一本簡單的地址簿,直接記錄著所有虛擬地址和物理地址的映射關系。然而,隨著計算機技術的發展和內存需求的不斷增大,一級頁表逐漸暴露出了它的不足。
假設在一個 32 位的系統中,虛擬地址空間為 4GB,頁面大小為 4KB,如果使用一級頁表,那么頁表項的數量將達到 1M(4GB / 4KB = 1M)個。每個頁表項假設占用 4 個字節,那么整個頁表就需要占用 4MB(1M * 4B = 4MB)的內存空間。而且,每個進程都需要有自己獨立的頁表,如果系統中有多個進程,那么頁表所占用的內存將是一個巨大的開銷,這就好比一個小書架要放下所有的書籍,顯然是不現實的。
為了解決一級頁表的這些問題,多級頁表應運而生,其中以二級頁表最為常見。二級頁表就像是一個更智能的圖書館管理系統,將地址映射關系分層管理。
在二級頁表中,虛擬地址被劃分為三個部分:最高位部分作為一級頁表(也稱為頁目錄)的索引,中間部分作為二級頁表(真正的頁表)的索引,最低位部分則是頁內偏移。當進行地址轉換時,MMU 首先根據虛擬地址的最高位部分,在一級頁表中找到對應的二級頁表的起始地址;然后,再根據虛擬地址的中間部分,在找到的二級頁表中查找對應的物理頁幀號(PFN);最后,將 PFN 和頁內偏移組合起來,得到物理地址。
例如,在一個 32 位系統中,使用二級頁表,虛擬地址 32 位被劃分為:高 10 位用于一級頁表索引,中間 10 位用于二級頁表索引,低 12 位為頁內偏移。假設虛擬地址為 0x12345678,首先提取高 10 位(假設為 0x123),在一級頁表中找到對應的二級頁表起始地址;然后提取中間 10 位(假設為 0x456),在找到的二級頁表中查找對應的 PFN;最后將 PFN 和低 12 位的頁內偏移(0x678)組合,得到物理地址。
通過這種方式,二級頁表大大減少了頁表所占用的內存空間。因為一級頁表只需要存儲二級頁表的起始地址,而不需要存儲所有的物理地址映射關系,只有當需要訪問某個二級頁表時,才會將其加載到內存中,就像圖書館只在需要某本書時才從倉庫中取出放到書架上,大大節省了書架的空間。同時,多級頁表也提高了地址轉換的效率,使得計算機系統能夠更加高效地管理內存 。
四、TLB與緩存
4.1TLB加速地址轉換的神器
在 MMU 進行地址轉換的過程中,有一個得力的小助手 ——TLB(Translation Lookaside Buffer,地址轉換后備緩沖器),它就像是一個高效的 “記憶小能手”,大大加速了地址轉換的過程。
我們知道,頁表通常存放在內存中,而內存訪問速度相對較慢。如果每次地址轉換都要訪問內存中的頁表,那將會極大地影響系統性能,就好比每次找東西都要去很遠的倉庫里翻找,效率會非常低。
而 TLB 作為頁表的高速緩存,存儲了近期最常訪問的頁表項。它的存在基于一個重要的原理 —— 局部性原理。局部性原理指出,程序在執行過程中,往往會在一段時間內集中訪問某些特定的內存區域,就像我們在日常生活中,經常會反復使用某些常用物品,而很少去碰那些不常用的東西。
當 CPU 需要進行地址轉換時,會首先在 TLB 中查找對應的頁表項。如果 TLB 中存在所需的頁表項,即發生 TLB 命中(TLB Hit),那么 MMU 就可以直接從 TLB 中獲取物理頁幀號(PFN),而不需要再去內存中查詢頁表,這就大大減少了地址轉換的時間,提高了轉換效率,就像我們在自己身邊的小抽屜里就能找到常用物品,而不用跑去遠處的大倉庫。
例如,假設一個程序頻繁訪問某個虛擬頁面,當第一次訪問時,MMU 會在內存中的頁表中查找對應的物理頁幀號,并將這個頁表項緩存到 TLB 中。當程序再次訪問這個虛擬頁面時,TLB 就能夠快速響應,直接提供物理頁幀號,使得地址轉換能夠快速完成。
只有當 TLB 中沒有找到所需的頁表項,即發生 TLB 失效(TLB Miss)時,MMU 才會去內存中查詢頁表,獲取物理頁幀號,并將這個頁表項更新到 TLB 中,以備下次使用,就像我們在小抽屜里找不到東西時,才會去大倉庫找,找到后會把它放在小抽屜里方便下次使用。
4.2緩存與MMU的協同工作
緩存(Cache)和 MMU 在數據訪問中緊密協同,共同為提高內存訪問速度而努力,它們就像是一對默契十足的搭檔;緩存是一種高速的存儲設備,位于 CPU 和內存之間,它存儲著 CPU 近期可能會頻繁訪問的數據和指令,就像一個隨時待命的小倉庫,里面存放著常用物資,能夠快速響應 CPU 的需求。
當 CPU 需要讀取數據時,它會首先向緩存發送請求,查找所需的數據。如果緩存中存在該數據,即發生緩存命中(Cache Hit),CPU 就可以直接從緩存中讀取數據,這比從內存中讀取數據要快得多,因為緩存的訪問速度比內存快很多倍,就像我們從身邊的小倉庫里取東西,比從遠處的大倉庫取東西要快得多。
如果緩存中沒有找到所需的數據,即發生緩存未命中(Cache Miss),CPU 就會向 MMU 發送請求,將虛擬地址轉換為物理地址,然后從內存中讀取數據。在這個過程中,MMU 負責將虛擬地址準確地轉換為物理地址,就像一個精準的導航儀,指引著 CPU 找到數據在內存中的實際位置。
當數據從內存中讀取出來后,不僅會被返回給 CPU,還會被緩存到緩存中,以備下次 CPU 訪問,這就像我們從大倉庫取完東西后,會把它放在身邊的小倉庫里,方便下次使用。
例如,當一個程序運行時,CPU 需要讀取某個變量的值。首先,CPU 會在緩存中查找這個變量,如果緩存命中,就可以立即獲取變量的值并繼續執行程序。如果緩存未命中,CPU 會通過 MMU 將虛擬地址轉換為物理地址,從內存中讀取變量的值,然后將這個變量及其相鄰的數據一起緩存到緩存中,這樣當下次 CPU 再次訪問這個變量或者其相鄰的數據時,就可以直接從緩存中獲取,大大提高了內存訪問速度。
通過緩存和 MMU 的協同工作,計算機系統能夠更高效地訪問內存,減少內存訪問延遲,提高程序的執行效率,就像一個高效的物流系統,通過合理的調配和協作,讓物資能夠快速、準確地送達目的地 。
五、MMU在Linux系統中的實戰應用
5.1進程內存管理實例
在 Linux 系統中,當我們啟動一個進程時,就像是在內存這個大舞臺上為這個進程開辟了一個專屬的小天地,而 MMU 在其中扮演著至關重要的角色。
以一個簡單的 C 程序為例,假設我們有如下代碼:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(10 * sizeof(int));
if (ptr == NULL) {
perror("malloc failed");
return 1;
}
for (int i = 0; i < 10; i++) {
ptr[i] = i;
printf("ptr[%d] = %d\n", i, ptr[i]);
}
free(ptr);
return 0;
}
當這個程序被執行,也就是進程被創建時,Linux 內核會為該進程分配一個獨立的虛擬地址空間,這個空間就像是進程自己的 “私人倉庫”,里面包含了代碼段、數據段、堆、棧等不同的區域,每個區域都有其特定的用途 。
在這個例子中,當執行malloc(10 * sizeof(int))時,程序向系統申請了一塊內存。如果申請的內存小于 128K(這個閾值可以通過M_MMAP_THRESHOLD選項調節),系統會使用brk系統調用分配內存,它會將數據段的最高地址指針_edata往高地址推,也就是在進程的堆區域分配虛擬內存空間。
此時,僅僅是分配了虛擬內存,還沒有分配物理內存。只有當程序第一次訪問這塊內存,比如執行ptr[i] = i;時,會引發內核缺頁中斷。這時,MMU 就開始發揮作用了,它會根據進程的頁表,查找對應的物理頁幀號(PFN),如果沒有找到,就會觸發一系列操作,如查找 / 分配一個物理頁,填充物理頁內容(可能是讀取磁盤,或者直接置 0,或者啥也不干),然后建立虛擬地址到物理地址的映射關系,這樣程序就可以順利訪問物理內存中的數據了 。
當程序執行完不再需要這塊內存時,調用free(ptr)釋放內存。對于brk分配的內存,只有當高地址內存釋放以后,低地址內存才有可能被釋放,這就可能導致內存碎片的產生。例如,如果后續還有內存分配請求,而這些內存碎片又無法滿足新的請求,就會影響內存的使用效率 。
如果malloc申請的內存大于 128K,系統則會使用mmap系統調用在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閑的虛擬內存進行分配。與brk不同的是,mmap分配的內存可以單獨釋放,這在一定程度上減少了內存碎片的問題 。
通過這個簡單的例子,我們可以看到 MMU 在進程內存管理中的關鍵作用,它為每個進程提供了獨立的虛擬內存空間,使得進程之間的內存訪問相互隔離,同時通過地址轉換和頁表管理,實現了虛擬地址到物理地址的映射,保證了進程能夠高效、安全地訪問內存 。
5.2內存分配與回收機制
在 Linux 系統中,內存的分配與回收是一個復雜而有序的過程,就像是一個繁忙的物流中心,不斷地接收和處理各種內存請求;從操作系統的角度來看,進程分配內存主要有兩種方式,分別由brk和mmap這兩個系統調用完成(這里不考慮共享內存)。
當申請小于 128K 的內存時,系統會使用brk分配內存。它就像是一個 “空間拓展者”,將數據段(.data)的最高地址指針_edata向高地址移動,從而增加堆的有效區域來申請新的內存空間。不過,此時分配的僅僅是虛擬內存空間,并沒有對應的物理內存,就像是畫了一個 “大餅”,還沒有真正把 “餅” 做出來。只有在第一次讀 / 寫數據時,才會引起內核缺頁中斷,這時內核才會去分配對應的物理內存,并建立虛擬內存空間和物理內存空間的映射關系 。
而當申請大于 128K 的內存時,系統會采用mmap分配內存。mmap會在進程的文件映射區(堆和棧中間的區域)找一塊空閑存儲空間來分配虛擬內存。這種方式分配的內存可以單獨釋放,具有更高的靈活性 ;在內存回收方面,Linux 系統有著一套完善的機制。當系統內存不足時,就需要回收一部分內存,以滿足新的內存請求。內存回收主要包括頁面回收、頁面交換、內存壓縮和匿名頁面丟棄等機制 。
頁面回收是當系統內存不足時,Linux 通過頁面回收機制釋放不再使用的頁面。這其中會用到 LRU(最近最少使用)算法,就像是一個 “淘汰篩選器”,它會選擇最近最少使用的頁面,并將其交換到磁盤上的交換分區(Swap)或丟棄頁面的內容,這樣就可以釋放出更多的內存供其他應用程序使用 ;頁面交換則是將不活躍的頁面移出物理內存,以釋放內存空間。當系統內存不足時,操作系統會將長時間未被訪問的頁面交換到磁盤上的交換分區。當需要訪問這些頁面時,操作系統又會將其重新調入內存 。
為了避免頻繁進行頁面交換,Linux 還引入了內存壓縮機制。它就像是一個 “空間壓縮大師”,通過使用壓縮算法將不活躍的頁面壓縮存儲在內存中,從而減少內存占用。當需要訪問被壓縮的頁面時,操作系統會將其解壓縮并重新放置在內存中 。
在某些情況下,Linux 還可以通過丟棄匿名頁面(不屬于文件系統緩存的頁面,通常是由進程使用的堆棧和堆分配的匿名頁面)來釋放內存。當系統內存不足時,操作系統可以選擇丟棄這些頁面以釋放內存 ;在這個過程中,MMU 始終發揮著重要作用。它通過維護頁表和地址轉換,確保內存的分配和回收過程能夠準確無誤地進行,保證了系統內存的高效利用和穩定運行 。