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