Linux內(nèi)存分頁(yè)機(jī)制:解鎖高效內(nèi)存管理方式
在Linux操作系統(tǒng)復(fù)雜而精妙的架構(gòu)體系中,內(nèi)存管理堪稱其中的核心與關(guān)鍵。想象一下,系統(tǒng)如同一個(gè)繁忙的大型工廠,內(nèi)存則是工廠里至關(guān)重要的原材料倉(cāng)庫(kù),各個(gè)進(jìn)程就像不同的生產(chǎn)線,它們都對(duì)內(nèi)存資源有著迫切的需求。如何高效地管理這些內(nèi)存,讓每一條生產(chǎn)線都能順暢運(yùn)轉(zhuǎn),避免資源浪費(fèi)與沖突,成了決定系統(tǒng)整體性能的關(guān)鍵因素。
在早期,分段機(jī)制曾在操作系統(tǒng)的內(nèi)存管理領(lǐng)域占據(jù)主導(dǎo)地位。但隨著技術(shù)的飛速發(fā)展,新的挑戰(zhàn)不斷涌現(xiàn),內(nèi)存碎片問(wèn)題日益凸顯,就如同倉(cāng)庫(kù)中的原材料被零散放置,難以被高效取用,這嚴(yán)重影響了內(nèi)存的使用效率與系統(tǒng)性能。為了應(yīng)對(duì)這一難題,分頁(yè)機(jī)制應(yīng)運(yùn)而生,它如同一位聰明的倉(cāng)庫(kù)管理員,重新規(guī)劃了內(nèi)存的存儲(chǔ)方式,將內(nèi)存分割成固定大小的小片,也就是內(nèi)存頁(yè),以此來(lái)提升內(nèi)存的空間利用率,逐漸成為現(xiàn)代操作系統(tǒng)內(nèi)存管理的主流選擇。今天,就讓我們一同深入探索 Linux 內(nèi)存分頁(yè)機(jī)制的奧秘,解鎖這一高效內(nèi)存管理方式背后的關(guān)鍵技術(shù) 。
一、內(nèi)存分頁(yè)機(jī)制
在 Linux 的世界里,內(nèi)存分頁(yè)機(jī)制就像是一位有條不紊的大管家,精心管理著系統(tǒng)的內(nèi)存資源。簡(jiǎn)單來(lái)說(shuō),內(nèi)存分頁(yè)機(jī)制就是把物理內(nèi)存和虛擬內(nèi)存分割成固定大小的小塊,這些小塊被稱作 “頁(yè)” ,每個(gè)頁(yè)的大小一般為 4KB 或者 8KB。就好比你有一個(gè)巨大的倉(cāng)庫(kù)(內(nèi)存),為了更好地管理里面的貨物(數(shù)據(jù)),你把倉(cāng)庫(kù)劃分成了一個(gè)個(gè)大小相同的小隔間(頁(yè))。
1.1什么是分頁(yè)機(jī)制
分頁(yè)機(jī)制是 80x86 內(nèi)存管理機(jī)制的第二部分。它在分段機(jī)制的基礎(chǔ)上完成虛擬地址到物理地址的轉(zhuǎn)換過(guò)程。分段機(jī)制把邏輯地址轉(zhuǎn)換成線性地址,而分頁(yè)機(jī)制則把線性地址轉(zhuǎn)換成物理地址。分頁(yè)機(jī)制可用于任何一種分段模型。處理器分頁(yè)機(jī)制會(huì)把線性地址空間劃分成頁(yè)面,然后這些線性地址空間頁(yè)面被映射到物理地址空間的頁(yè)面上。分頁(yè)機(jī)制的幾種頁(yè)面級(jí)保護(hù)措施,可和分段機(jī)制保護(hù)措施或用或替代分段機(jī)制的保護(hù)措施。
1.2分頁(yè)機(jī)制如何啟用
在我們進(jìn)行程序開(kāi)發(fā)的時(shí)候,一般情況下,是不需要管理內(nèi)存的,也不需要操心內(nèi)存夠不夠用,其實(shí),這就是分頁(yè)機(jī)制給我們帶來(lái)的好處。它是實(shí)現(xiàn)虛擬存儲(chǔ)的關(guān)鍵,位于線性地址與物理地址之間,在使用這種內(nèi)存分頁(yè)管理方法時(shí),每個(gè)執(zhí)行中的進(jìn)程(任務(wù))可以使用比實(shí)際內(nèi)存容量大得多的連續(xù)地址空間。而且當(dāng)系統(tǒng)內(nèi)存實(shí)際上被分成很多凌亂的塊時(shí),它可以建立一個(gè)大而連續(xù)的內(nèi)存空間的映象,好讓程序不用操心和管理這些分散的內(nèi)存塊。分頁(yè)機(jī)制增強(qiáng)了分段機(jī)制的性能。頁(yè)地址變換是建立在段變換基礎(chǔ)之上的。因?yàn)椋喂芾頇C(jī)制對(duì)于Intel處理器來(lái)說(shuō)是最基本的,任何時(shí)候都無(wú)法關(guān)閉。所以即使啟用了頁(yè)管理功能,分段機(jī)制依然是起作用的,段部件也依然工作。
分頁(yè)只能在保護(hù)模式(CR0.PE = 1)下使用。在保護(hù)模式下,是否開(kāi)啟分頁(yè),由 CR0. PG 位(位 31)決定:
- 當(dāng) CR0.PG = 0 時(shí),未開(kāi)啟分頁(yè),線性地址等同于物理地址;
- 當(dāng) CR0.PG = 1 時(shí),開(kāi)啟分頁(yè)。
1.3分頁(yè)機(jī)制線性地址到物理地址轉(zhuǎn)換過(guò)程
80x86使用 4K 字節(jié)固定大小的頁(yè)面,每個(gè)頁(yè)面均是 4KB,并且對(duì)其于 4K 地址邊界處。這表示分頁(yè)機(jī)制把 2^32字節(jié)(4GB)的線性地址空間劃分成 2^20(1M = 1048576)個(gè)頁(yè)面。分頁(yè)機(jī)制通過(guò)把線性地址空間中的頁(yè)面重新定位到物理地址空間中進(jìn)行操作。由于 4K 大小的頁(yè)面作為一個(gè)單元進(jìn)行映射,并且對(duì)其于 4K 邊界,因此線性地址的低 12 位可做為頁(yè)內(nèi)偏移地量直接作為物理地址的低 12 位。分頁(yè)機(jī)制執(zhí)行的重定向功能可以看作是把線性地址的高 20 位轉(zhuǎn)換到對(duì)應(yīng)物理地址的高 20 位。
線性到物理地址轉(zhuǎn)換功能,被擴(kuò)展成允許一個(gè)線性地址被標(biāo)注為無(wú)效的,而非要讓其產(chǎn)生一個(gè)物理地址。以下兩種情況一個(gè)頁(yè)面可以被標(biāo)注為無(wú)效的:
1. 操作系統(tǒng)不支持的線性地址。
2. 對(duì)應(yīng)的虛擬內(nèi)存系統(tǒng)中的頁(yè)面在磁盤(pán)上而非在物理內(nèi)存中。
在第一中情況下,產(chǎn)生無(wú)效地址的程序必須被終止,在第二種情況下,該無(wú)效地址實(shí)際上是請(qǐng)求 操作系統(tǒng)虛擬內(nèi)存管理器 把對(duì)應(yīng)的頁(yè)面從磁盤(pán)加載到物理內(nèi)存中,以供程序訪問(wèn)。因?yàn)闊o(wú)效頁(yè)面通常與虛擬存儲(chǔ)系統(tǒng)相關(guān),因此它們被稱為不存在頁(yè)面,由頁(yè)表中稱為存在的屬性來(lái)確定。
當(dāng)使用分頁(yè)時(shí),處理器會(huì)把線性地址空間劃分成固定大小的頁(yè)面(4KB),這些頁(yè)面可以映射到物理內(nèi)存中或磁盤(pán)存儲(chǔ)空間中,當(dāng)一個(gè)程序引用內(nèi)存中的邏輯地址時(shí),處理器會(huì)把該邏輯地址轉(zhuǎn)換成一個(gè)線性地址,然后使用分頁(yè)機(jī)制把該線性地址轉(zhuǎn)換成對(duì)應(yīng)的物理地址。
如果包含線性地址的頁(yè)面不在當(dāng)前物理內(nèi)存中,處理器就會(huì)產(chǎn)生一個(gè)頁(yè)錯(cuò)誤異常。頁(yè)錯(cuò)誤異常處理程序就會(huì)讓操作系統(tǒng)從磁盤(pán)中把相應(yīng)頁(yè)面加載到物理內(nèi)存中(操作過(guò)程中可能會(huì)把物理內(nèi)存中不同的頁(yè)面寫(xiě)到磁盤(pán)上)。當(dāng)頁(yè)面加載到物理內(nèi)存之后,從異常處理過(guò)程的返回操作會(huì)使異常的指令被重新執(zhí)行。處理器把用于線性地址轉(zhuǎn)換成物理地址和用于產(chǎn)生頁(yè)錯(cuò)誤的信息包含在存儲(chǔ)與內(nèi)存中的頁(yè)目錄與頁(yè)表中。
1.4分頁(yè)機(jī)制與分段機(jī)制的不同
分頁(yè)與分段的最大的不同之處在于分頁(yè)使用了固定長(zhǎng)度的頁(yè)面。段的長(zhǎng)度通常與存放在其中的代碼或數(shù)據(jù)結(jié)構(gòu)有相同的長(zhǎng)度。與段不同,頁(yè)面有固定的長(zhǎng)度。如果僅使用分段地址轉(zhuǎn)換,那么存儲(chǔ)在物理內(nèi)存中的一個(gè)數(shù)據(jù)結(jié)構(gòu)將包含其所有的部分。如果使用了分頁(yè),那么一個(gè)數(shù)據(jù)結(jié)構(gòu)就可以一部分存儲(chǔ)與物理內(nèi)存中,而另一部分保存在磁盤(pán)中。
為了減少地址轉(zhuǎn)換所要求的總線周期數(shù)量,最近訪問(wèn)的頁(yè)目錄和頁(yè)表會(huì)被存放在處理器的一個(gè)叫做轉(zhuǎn)換查找緩沖區(qū)(TLB)的緩沖器件中。TLB 可以滿足大多數(shù)讀頁(yè)目錄和頁(yè)表的請(qǐng)求而無(wú)需使用總線周期。只有當(dāng) TLB 中不包含所要求的頁(yè)表項(xiàng)是才會(huì)出現(xiàn)使用額外的總線周期從內(nèi)存讀取頁(yè)表項(xiàng)。通常在一個(gè)頁(yè)表項(xiàng)很長(zhǎng)時(shí)間沒(méi)有訪問(wèn)過(guò)時(shí)才會(huì)出現(xiàn)這種情況。
二、分頁(yè)機(jī)制類型
2.1四級(jí)分頁(yè)機(jī)制
前面我們提到Linux內(nèi)核僅使用了較少的分段機(jī)制,但是卻對(duì)分頁(yè)機(jī)制的依賴性很強(qiáng),其使用一種適合32位和64位結(jié)構(gòu)的通用分頁(yè)模型,該模型使用四級(jí)分頁(yè)機(jī)制,即
- 頁(yè)全局目錄(Page Global Directory)
- 頁(yè)上級(jí)目錄(Page Upper Directory)
- 頁(yè)中間目錄(Page Middle Directory)
- 頁(yè)表(Page Table)
頁(yè)全局目錄包含若干頁(yè)上級(jí)目錄的地址:
- 頁(yè)上級(jí)目錄又依次包含若干頁(yè)中間目錄的地址;
- 而頁(yè)中間目錄又包含若干頁(yè)表的地址;
- 每一個(gè)頁(yè)表項(xiàng)指向一個(gè)頁(yè)框。
因此線性地址因此被分成五個(gè)部分,而每一部分的大小與具體的計(jì)算機(jī)體系結(jié)構(gòu)有關(guān)。
2.2不同架構(gòu)的分頁(yè)機(jī)制
對(duì)于不同的體系結(jié)構(gòu),Linux采用的四級(jí)頁(yè)表目錄的大小有所不同:對(duì)于i386而言,僅采用二級(jí)頁(yè)表,即頁(yè)上層目錄和頁(yè)中層目錄長(zhǎng)度為0;對(duì)于啟用PAE的i386,采用了三級(jí)頁(yè)表,即頁(yè)上層目錄長(zhǎng)度為0;對(duì)于64位體系結(jié)構(gòu),可以采用三級(jí)或四級(jí)頁(yè)表,具體選擇由硬件決定。
對(duì)于沒(méi)有啟用物理地址擴(kuò)展的32位系統(tǒng),兩級(jí)頁(yè)表已經(jīng)足夠了。從本質(zhì)上說(shuō)Linux通過(guò)使“頁(yè)上級(jí)目錄”位和“頁(yè)中間目錄”位全為0,徹底取消了頁(yè)上級(jí)目錄和頁(yè)中間目錄字段。不過(guò),頁(yè)上級(jí)目錄和頁(yè)中間目錄在指針序列中的位置被保留,以便同樣的代碼在32位系統(tǒng)和64位系統(tǒng)下都能使用。內(nèi)核為頁(yè)上級(jí)目錄和頁(yè)中間目錄保留了一個(gè)位置,這是通過(guò)把它們的頁(yè)目錄項(xiàng)數(shù)設(shè)置為1,并把這兩個(gè)目錄項(xiàng)映射到頁(yè)全局目錄的一個(gè)合適的目錄項(xiàng)而實(shí)現(xiàn)的。
啟用了物理地址擴(kuò)展的32 位系統(tǒng)使用了三級(jí)頁(yè)表。Linux 的頁(yè)全局目錄對(duì)應(yīng)80x86 的頁(yè)目錄指針表(PDPT),取消了頁(yè)上級(jí)目錄,頁(yè)中間目錄對(duì)應(yīng)80x86的頁(yè)目錄,Linux的頁(yè)表對(duì)應(yīng)80x86的頁(yè)表。
最終,64位系統(tǒng)使用三級(jí)還是四級(jí)分頁(yè)取決于硬件對(duì)線性地址的位的劃分。
為什么linux熱衷:分頁(yè)>分段
那么,為什么Linux是如此地?zé)嶂允褂梅猪?yè)技術(shù)而對(duì)分段機(jī)制表現(xiàn)得那么地冷淡呢,因?yàn)長(zhǎng)inux的進(jìn)程處理很大程度上依賴于分頁(yè)。事實(shí)上,線性地址到物理地址的自動(dòng)轉(zhuǎn)換使下面的設(shè)計(jì)目標(biāo)變得可行:
給每一個(gè)進(jìn)程分配一塊不同的物理地址空間,這確保了可以有效地防止尋址錯(cuò)誤。
區(qū)別頁(yè)(即一組數(shù)據(jù))和頁(yè)框(即主存中的物理地址)之不同。這就允許存放在某個(gè)頁(yè)框中的一個(gè)頁(yè),然后保存到磁盤(pán)上,以后重新裝入這同一頁(yè)時(shí)又被裝在不同的頁(yè)框中。這就是虛擬內(nèi)存機(jī)制的基本要素。
每一個(gè)進(jìn)程有它自己的頁(yè)全局目錄和自己的頁(yè)表集。當(dāng)發(fā)生進(jìn)程切換時(shí),Linux把cr3控制寄存器的內(nèi)容保存在前一個(gè)執(zhí)行進(jìn)程的描述符中,然后把下一個(gè)要執(zhí)行進(jìn)程的描述符的值裝入cr3寄存器中。因此,當(dāng)新進(jìn)程重新開(kāi)始在CPU上執(zhí)行時(shí),分頁(yè)單元指向一組正確的頁(yè)表。
把線性地址映射到物理地址雖然有點(diǎn)復(fù)雜,但現(xiàn)在已經(jīng)成了一種機(jī)械式的任務(wù)。
三、分頁(yè)機(jī)制的工作原理
3.1分頁(yè)技術(shù)核心思想
分頁(yè)技術(shù)的核心思想,是把虛擬地址空間和物理內(nèi)存都劃分成固定大小的頁(yè),然后通過(guò)頁(yè)表來(lái)建立虛擬頁(yè)到物理頁(yè)框的映射關(guān)系。打個(gè)比方,你有一本厚厚的字典(虛擬地址空間),為了快速找到某個(gè)字(數(shù)據(jù)),你給字典的每一頁(yè)(虛擬頁(yè))都編了號(hào),同時(shí)在另一張紙上(頁(yè)表)記錄了每個(gè)編號(hào)對(duì)應(yīng)的實(shí)際頁(yè)碼(物理頁(yè)框)。
以 x86_64 架構(gòu)的 4 級(jí)頁(yè)表結(jié)構(gòu)為例,虛擬地址被分成了多個(gè)部分 。其中,[47-39] 位表示頁(yè)全局目錄(PGD),[38-30] 位表示上層頁(yè)目錄(PUD),[29-21] 位表示中間頁(yè)目錄(PMD),[20-12] 位表示頁(yè)表項(xiàng)(PTE),最后的 [11-0] 位則是頁(yè)內(nèi)偏移 。CPU 在訪問(wèn)內(nèi)存時(shí),會(huì)先通過(guò) CR3 寄存器找到頁(yè)全局目錄,然后根據(jù)虛擬地址中的 PGD 部分找到對(duì)應(yīng)的上層頁(yè)目錄,依此類推,逐級(jí)查詢,最終找到物理頁(yè)框號(hào),再結(jié)合頁(yè)內(nèi)偏移,就能得到準(zhǔn)確的物理地址。這個(gè)過(guò)程就像是你按照索引在多層書(shū)架上找一本書(shū),每一層索引都能幫你縮小查找范圍,最終準(zhǔn)確找到目標(biāo)書(shū)籍。
3.2多級(jí)頁(yè)表設(shè)計(jì)動(dòng)機(jī)與優(yōu)勢(shì)
在早期的內(nèi)存管理中,曾采用過(guò)單級(jí)頁(yè)表,即通過(guò)一個(gè)頁(yè)表直接將虛擬地址映射到物理地址。然而,這種方式存在明顯的弊端。隨著虛擬地址空間的不斷增大,例如在 64 位系統(tǒng)中,若使用單級(jí)頁(yè)表來(lái)映射所有虛擬地址,所需的頁(yè)表空間將變得極為龐大。以 48 位地址空間為例,若每個(gè)頁(yè)表項(xiàng)大小為 8 字節(jié),計(jì)算可知單級(jí)頁(yè)表需要 256TB 的內(nèi)存空間,這在實(shí)際應(yīng)用中幾乎是不可行的,不僅會(huì)占用大量的內(nèi)存資源,而且管理和維護(hù)如此巨大的頁(yè)表也會(huì)面臨諸多困難。
為了解決單級(jí)頁(yè)表的空間占用問(wèn)題,多級(jí)頁(yè)表應(yīng)運(yùn)而生。多級(jí)頁(yè)表的設(shè)計(jì)采用了稀疏存儲(chǔ)的策略,它并不是為整個(gè)虛擬地址空間都分配頁(yè)表,而是僅在需要時(shí)才分配實(shí)際使用的頁(yè)表項(xiàng)。這樣一來(lái),大大節(jié)省了內(nèi)存空間。
以 Linux 的四級(jí)頁(yè)表為例,當(dāng)一個(gè)進(jìn)程只使用 1GB 內(nèi)存時(shí),若采用四級(jí)頁(yè)表,其總大小約為 (1GB/2MB × 4 × 8B = 16KB) 。這是因?yàn)樵谒募?jí)頁(yè)表結(jié)構(gòu)中,每一級(jí)頁(yè)表都起到了篩選和定位的作用。當(dāng)進(jìn)程訪問(wèn)某個(gè)虛擬地址時(shí),首先通過(guò) CR3 寄存器找到頁(yè)全局目錄(PGD),PGD 根據(jù)虛擬地址中的相應(yīng)部分,找到上層頁(yè)目錄(PUD),若該 PUD 對(duì)應(yīng)的頁(yè)表項(xiàng)存在且有效,則繼續(xù)通過(guò) PUD 找到中間頁(yè)目錄(PMD),依此類推。如果在某一級(jí)發(fā)現(xiàn)對(duì)應(yīng)的頁(yè)表項(xiàng)不存在,說(shuō)明該虛擬地址尚未被映射到物理內(nèi)存,系統(tǒng)會(huì)根據(jù)需要?jiǎng)討B(tài)分配頁(yè)表項(xiàng),而不是預(yù)先分配整個(gè)頁(yè)表。
這種按需分配的方式,使得頁(yè)表僅占用實(shí)際使用的內(nèi)存區(qū)域,避免了為未使用的虛擬地址空間分配頁(yè)表所造成的內(nèi)存浪費(fèi) 。同時(shí),多級(jí)頁(yè)表的結(jié)構(gòu)也使得內(nèi)存管理更加靈活和高效,能夠更好地適應(yīng)不同進(jìn)程對(duì)內(nèi)存的需求。
四、linux中頁(yè)表處理數(shù)據(jù)結(jié)構(gòu)
分頁(yè)轉(zhuǎn)換功能由駐留在內(nèi)存中的表來(lái)描述,該表稱為頁(yè)表,存放在物理地址空間中。頁(yè)表可以看作是簡(jiǎn)單的 2^20 物理地址數(shù)組。線性到物理地址的映射功能可以簡(jiǎn)單地看作進(jìn)行數(shù)組查找。線性地址的高 20 位構(gòu)成這個(gè)數(shù)組的索引值,用于選擇對(duì)應(yīng)頁(yè)面的物理(基)地址。線性地址的低 12 位給出了頁(yè)面中的偏移量,加上頁(yè)面的基地址最終形成對(duì)應(yīng)的物理地址。由于頁(yè)面基地址對(duì)齊在 4K 邊界上,因此頁(yè)面基地址的低 12 為肯定是 0 ,這意味著 高 20 位的頁(yè)面基地址 和 12 位偏移地址連接組合在一起就能得到對(duì)應(yīng)的物理地址。
頁(yè)表中每個(gè)頁(yè)表項(xiàng) 大小為 32 位,由于只需其中的 20 位來(lái)存放頁(yè)面的物理基地址,因此剩下的 12 位可用于存放諸如頁(yè)面是否存在等的屬性信息。如果線性地址索引的頁(yè)表被標(biāo)注為存在,則表示該項(xiàng)有效, 我們可以從中取得頁(yè)面的物理地址。如果項(xiàng)中表明不存在,那么當(dāng)當(dāng)訪問(wèn)對(duì)應(yīng)物理界面時(shí)就會(huì)產(chǎn)生一個(gè)異常。
4.1 頁(yè)表類型定義
(1)pgd_t、pmd_t、pud_t和pte_t
Linux分別采用pgd_t、pmd_t、pud_t和pte_t四種數(shù)據(jù)結(jié)構(gòu)來(lái)表示頁(yè)全局目錄項(xiàng)、頁(yè)上級(jí)目錄項(xiàng)、頁(yè)中間目錄項(xiàng)和頁(yè)表項(xiàng)。這四種 數(shù)據(jù)結(jié)構(gòu)本質(zhì)上都是無(wú)符號(hào)長(zhǎng)整型unsigned long
Linux為了更嚴(yán)格數(shù)據(jù)類型檢查,將無(wú)符號(hào)長(zhǎng)整型unsigned long分別封裝成四種不同的頁(yè)表項(xiàng)。如果不采用這種方法,那么一個(gè)無(wú)符號(hào)長(zhǎng)整型數(shù)據(jù)可以傳入任何一個(gè)與四種頁(yè)表相關(guān)的函數(shù)或宏中,這將大大降低程序的健壯性。
pgprot_t是另一個(gè)64位(PAE激活時(shí))或32位(PAE禁用時(shí))的數(shù)據(jù)類型,它表示與一個(gè)單獨(dú)表項(xiàng)相關(guān)的保護(hù)標(biāo)志。首先我們查看一下子這些類型是如何定義的:
①pteval_t,pmdval_t,pudval_t,pgdval_t
參照arch/x86/include/asm/pgtable_64_types.h
#ifndef __ASSEMBLY__
#include <linux/types.h>
/*
* These are used to make use of C type-checking..
*/
typedef unsigned long pteval_t;
typedef unsigned long pmdval_t;
typedef unsigned long pudval_t;
typedef unsigned long pgdval_t;
typedef unsigned long pgprotval_t;
typedef struct { pteval_t pte; } pte_t;
#endif /* !__ASSEMBLY__ */
②pgd_t、pmd_t、pud_t和pte_t
參照 /arch/x86/include/asm/pgtable_types.h
typedef struct { pgdval_t pgd; } pgd_t;
static inline pgd_t native_make_pgd(pgdval_t val)
{
return (pgd_t) { val };
}
static inline pgdval_t native_pgd_val(pgd_t pgd)
{
return pgd.pgd;
}
static inline pgdval_t pgd_flags(pgd_t pgd)
{
return native_pgd_val(pgd) & PTE_FLAGS_MASK;
}
#if CONFIG_PGTABLE_LEVELS > 3
typedef struct { pudval_t pud; } pud_t;
static inline pud_t native_make_pud(pmdval_t val)
{
return (pud_t) { val };
}
static inline pudval_t native_pud_val(pud_t pud)
{
return pud.pud;
}
#else
#include <asm-generic/pgtable-nopud.h>
static inline pudval_t native_pud_val(pud_t pud)
{
return native_pgd_val(pud.pgd);
}
#endif
#if CONFIG_PGTABLE_LEVELS > 2
typedef struct { pmdval_t pmd; } pmd_t;
static inline pmd_t native_make_pmd(pmdval_t val)
{
return (pmd_t) { val };
}
static inline pmdval_t native_pmd_val(pmd_t pmd)
{
return pmd.pmd;
}
#else
#include <asm-generic/pgtable-nopmd.h>
static inline pmdval_t native_pmd_val(pmd_t pmd)
{
return native_pgd_val(pmd.pud.pgd);
}
#endif
static inline pudval_t pud_pfn_mask(pud_t pud)
{
if (native_pud_val(pud) & _PAGE_PSE)
return PHYSICAL_PUD_PAGE_MASK;
else
return PTE_PFN_MASK;
}
static inline pudval_t pud_flags_mask(pud_t pud)
{
return ~pud_pfn_mask(pud);
}
static inline pudval_t pud_flags(pud_t pud)
{
return native_pud_val(pud) & pud_flags_mask(pud);
}
static inline pmdval_t pmd_pfn_mask(pmd_t pmd)
{
if (native_pmd_val(pmd) & _PAGE_PSE)
return PHYSICAL_PMD_PAGE_MASK;
else
return PTE_PFN_MASK;
}
static inline pmdval_t pmd_flags_mask(pmd_t pmd)
{
return ~pmd_pfn_mask(pmd);
}
static inline pmdval_t pmd_flags(pmd_t pmd)
{
return native_pmd_val(pmd) & pmd_flags_mask(pmd);
}
static inline pte_t native_make_pte(pteval_t val)
{
return (pte_t) { .pte = val };
}
static inline pteval_t native_pte_val(pte_t pte)
{
return pte.pte;
}
static inline pteval_t pte_flags(pte_t pte)
{
return native_pte_val(pte) & PTE_FLAGS_MASK;
}③xxx_val和__xxx
參照/arch/x86/include/asm/pgtable.h
五個(gè)類型轉(zhuǎn)換宏(_ pte、_ pmd、_ pud、_ pgd和__ pgprot)把一個(gè)無(wú)符號(hào)整數(shù)轉(zhuǎn)換成所需的類型。
另外的五個(gè)類型轉(zhuǎn)換宏(pte_val,pmd_val, pud_val, pgd_val和pgprot_val)執(zhí)行相反的轉(zhuǎn)換,即把上面提到的四種特殊的類型轉(zhuǎn)換成一個(gè)無(wú)符號(hào)整數(shù)。
#define pgd_val(x) native_pgd_val(x)
#define __pgd(x) native_make_pgd(x)
#ifndef __PAGETABLE_PUD_FOLDED
#define pud_val(x) native_pud_val(x)
#define __pud(x) native_make_pud(x)
#endif
#ifndef __PAGETABLE_PMD_FOLDED
#define pmd_val(x) native_pmd_val(x)
#define __pmd(x) native_make_pmd(x)
#endif
#define pte_val(x) native_pte_val(x)
#define __pte(x) native_make_pte(x)這里需要區(qū)別指向頁(yè)表項(xiàng)的指針和頁(yè)表項(xiàng)所代表的數(shù)據(jù)。以pgd_t類型為例子,如果已知一個(gè)pgd_t類型的指針pgd,那么通過(guò)pgd_val(*pgd)即可獲得該頁(yè)表項(xiàng)(也就是一個(gè)無(wú)符號(hào)長(zhǎng)整型數(shù)據(jù)),這里利用了面向?qū)ο蟮乃枷搿?/span>
4.2頁(yè)表描述宏
參照arch/x86/include/asm/pgtable_64
linux中使用下列宏簡(jiǎn)化了頁(yè)表處理,對(duì)于每一級(jí)頁(yè)表都使用有以下三個(gè)關(guān)鍵描述宏:
宏字段 | 描述 |
XXX_SHIFT | 指定Offset字段的位數(shù) |
XXX_SIZE | 頁(yè)的大小 |
XXX_MASK | 用以屏蔽Offset字段的所有位 |
我們的四級(jí)頁(yè)表,對(duì)應(yīng)的宏分別由PAGE,PMD,PUD,PGDIR
宏字段前綴 | 描述 |
PGDIR | 頁(yè)全局目錄(Page Global Directory) |
PUD | 頁(yè)上級(jí)目錄(Page Upper Directory) |
PMD | 頁(yè)中間目錄(Page Middle Directory) |
PAGE | 頁(yè)表(Page Table) |
PAGE宏–頁(yè)表(Page Table)
字段 | 描述 |
PAGE_MASK | 用以屏蔽Offset字段的所有位 |
PAGE_SHIFT | 指定Offset字段的位數(shù) |
PAGE_SIZE | 頁(yè)的大小 |
定義如下,在/arch/x86/include/asm/page_types.h文件中
/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))當(dāng)用于80x86處理器時(shí),PAGE_SHIFT返回的值為12,由于頁(yè)內(nèi)所有地址都必須放在Offset字段, 因此80x86系統(tǒng)的頁(yè)的大小PAGE_SIZE是2^12=4096字節(jié),PAGE_MASK宏產(chǎn)生的值為0xfffff000,用以屏蔽Offset字段的所有位。
PMD-Page Middle Directory (頁(yè)目錄)
字段 | 描述 |
PMD_SHIFT | 指定線性地址的Offset和Table字段的總位數(shù);換句話說(shuō),是頁(yè)中間目錄項(xiàng)可以映射的區(qū)域大小的對(duì)數(shù) |
PMD_SIZE | 用于計(jì)算由頁(yè)中間目錄的一個(gè)單獨(dú)表項(xiàng)所映射的區(qū)域大小,也就是一個(gè)頁(yè)表的大小 |
PMD_MASK | 用于屏蔽Offset字段與Table字段的所有位 |
當(dāng)PAE 被禁用時(shí),PMD_SHIFT 產(chǎn)生的值為22(來(lái)自O(shè)ffset 的12 位加上來(lái)自Table 的10 位),PMD_SIZE 產(chǎn)生的值為222 或 4 MB,PMD_MASK產(chǎn)生的值為 0xffc00000。
相反,當(dāng)PAE被激活時(shí),PMD_SHIFT 產(chǎn)生的值為21 (來(lái)自O(shè)ffset的12位加上來(lái)自Table的9位),PMD_SIZE 產(chǎn)生的值為2^21 或2 MB PMD_MASK產(chǎn)生的值為 0xffe00000。
大型頁(yè)不使用最后一級(jí)頁(yè)表,所以產(chǎn)生大型頁(yè)尺寸的LARGE_PAGE_SIZE 宏等于PMD_SIZE(2PMD_SHIFT),而在大型頁(yè)地址中用于屏蔽Offset字段和Table字段的所有位的LARGE_PAGE_MASK宏,就等于PMD_MASK。
PUD_SHIFT-頁(yè)上級(jí)目錄(Page Upper Directory)
字段 | 描述 |
PUD_SHIFT | 確定頁(yè)上級(jí)目錄項(xiàng)能映射的區(qū)域大小的位數(shù) |
PUD_SIZE | 用于計(jì)算頁(yè)全局目錄中的一個(gè)單獨(dú)表項(xiàng)所能映射的區(qū)域大小。 |
PUD_MASK | 用于屏蔽Offset字段,Table字段,Middle Air字段和Upper Air字段的所有位 |
在80x86處理器上,PUD_SHIFT總是等價(jià)于PMD_SHIFT,而PUD_SIZE則等于4MB或2MB。
PGDIR_SHIFT-頁(yè)全局目錄(Page Global Directory)
字段 | 描述 |
PGDIR_SHIFT | 確定頁(yè)全局頁(yè)目錄項(xiàng)能映射的區(qū)域大小的位數(shù) |
PGDIR_SIZE | 用于計(jì)算頁(yè)全局目錄中一個(gè)單獨(dú)表項(xiàng)所能映射區(qū)域的大小 |
PGDIR_MASK | 用于屏蔽Offset, Table,Middle Air及Upper Air的所有位 |
當(dāng)PAE 被禁止時(shí),PGDIR_SHIFT 產(chǎn)生的值為22(與PMD_SHIFT 和PUD_SHIFT 產(chǎn)生的值相同),PGDIR_SIZE 產(chǎn)生的值為 222 或 4 MB,PGDIR_MASK 產(chǎn)生的值為 0xffc00000。
相反,當(dāng)PAE被激活時(shí),PGDIR_SHIFT 產(chǎn)生的值為30 (12 位Offset 加 9 位Table再加 9位 Middle Air),PGDIR_SIZE 產(chǎn)生的值為230 或 1 GBPGDIR_MASK產(chǎn)生的值為0xc0000000
PTRS_PER_PTE, PTRS_PER_PMD, PTRS_PER_PUD以及PTRS_PER_PGD用于計(jì)算頁(yè)表、頁(yè)中間目錄、頁(yè)上級(jí)目錄和頁(yè)全局目錄表中表項(xiàng)的個(gè)數(shù)。當(dāng)PAE被禁止時(shí),它們產(chǎn)生的值分別為1024,1,1和1024。當(dāng)PAE被激活時(shí),產(chǎn)生的值分別為512,512,1和4。
4.3頁(yè)表處理函數(shù)
內(nèi)核還提供了許多宏和函數(shù)用于讀或修改頁(yè)表表項(xiàng):
- 如果相應(yīng)的表項(xiàng)值為0,那么,宏pte_none、pmd_none、pud_none和 pgd_none產(chǎn)生的值為1,否則產(chǎn)生的值為0。
- 宏pte_clear、pmd_clear、pud_clear和 pgd_clear清除相應(yīng)頁(yè)表的一個(gè)表項(xiàng),由此禁止進(jìn)程使用由該頁(yè)表項(xiàng)映射的線性地址。ptep_get_and_clear( )函數(shù)清除一個(gè)頁(yè)表項(xiàng)并返回前一個(gè)值。
- set_pte,set_pmd,set_pud和set_pgd向一個(gè)頁(yè)表項(xiàng)中寫(xiě)入指定的值。set_pte_atomic與set_pte作用相同,但是當(dāng)PAE被激活時(shí)它同樣能保證64位的值能被原子地寫(xiě)入。
- 如果a和b兩個(gè)頁(yè)表項(xiàng)指向同一頁(yè)并且指定相同訪問(wèn)優(yōu)先級(jí),pte_same(a,b)返回1,否則返回0。
- 如果頁(yè)中間目錄項(xiàng)指向一個(gè)大型頁(yè)(2MB或4MB),pmd_large(e)返回1,否則返回0。
宏pmd_bad由函數(shù)使用并通過(guò)輸入?yún)?shù)傳遞來(lái)檢查頁(yè)中間目錄項(xiàng)。如果目錄項(xiàng)指向一個(gè)不能使用的頁(yè)表,也就是說(shuō),如果至少出現(xiàn)以下條件中的一個(gè),則這個(gè)宏產(chǎn)生的值為1:
- 頁(yè)不在主存中(Present標(biāo)志被清除)。
- 頁(yè)只允許讀訪問(wèn)(Read/Write標(biāo)志被清除)。
- Acessed或者Dirty位被清除(對(duì)于每個(gè)現(xiàn)有的頁(yè)表,Linux總是強(qiáng)制設(shè)置這些標(biāo)志)。
pud_bad宏和pgd_bad宏總是產(chǎn)生0。沒(méi)有定義pte_bad宏,因?yàn)轫?yè)表項(xiàng)引用一個(gè)不在主存中的頁(yè),一個(gè)不可寫(xiě)的頁(yè)或一個(gè)根本無(wú)法訪問(wèn)的頁(yè)都是合法的。
如果一個(gè)頁(yè)表項(xiàng)的Present標(biāo)志或者Page Size標(biāo)志等于1,則pte_present宏產(chǎn)生的值為1,否則為0。
前面講過(guò)頁(yè)表項(xiàng)的Page Size標(biāo)志對(duì)微處理器的分頁(yè)部件來(lái)講沒(méi)有意義,然而,對(duì)于當(dāng)前在主存中卻又沒(méi)有讀、寫(xiě)或執(zhí)行權(quán)限的頁(yè),內(nèi)核將其Present和Page Size分別標(biāo)記為0和1。
這樣,任何試圖對(duì)此類頁(yè)的訪問(wèn)都會(huì)引起一個(gè)缺頁(yè)異常,因?yàn)轫?yè)的Present標(biāo)志被清0,而內(nèi)核可以通過(guò)檢查Page Size的值來(lái)檢測(cè)到產(chǎn)生異常并不是因?yàn)槿表?yè)。
如果相應(yīng)表項(xiàng)的Present標(biāo)志等于1,也就是說(shuō),如果對(duì)應(yīng)的頁(yè)或頁(yè)表被裝載入主存,pmd_present宏產(chǎn)生的值為1。pud_present宏和pgd_present宏產(chǎn)生的值總是1。
(1)查詢頁(yè)表項(xiàng)中任意一個(gè)標(biāo)志的當(dāng)前值
下表中列出的函數(shù)用來(lái)查詢頁(yè)表項(xiàng)中任意一個(gè)標(biāo)志的當(dāng)前值;除了pte_file()外,其他函數(shù)只有在pte_present返回1的時(shí)候,才能正常返回頁(yè)表項(xiàng)中任意一個(gè)標(biāo)志。
- pte_user( ):讀 User/Supervisor 標(biāo)志
- pte_read( ):讀 User/Supervisor 標(biāo)志(表示 80x86 處理器上的頁(yè)不受讀的保護(hù))
- pte_write( ):讀 Read/Write 標(biāo)志
- pte_exec( ):讀 User/Supervisor 標(biāo)志( 80x86 處理器上的頁(yè)不受代碼執(zhí)行的保護(hù))
- pte_dirty( ):讀 Dirty 標(biāo)志
- pte_young( ):讀 Accessed 標(biāo)志
- pte_file( ):讀 Dirty 標(biāo)志(當(dāng) Present 標(biāo)志被清除而 Dirty 標(biāo)志被設(shè)置時(shí),頁(yè)屬于一個(gè)非線性磁盤(pán)文件映射)
(2)設(shè)置頁(yè)表項(xiàng)中各標(biāo)志的值
下表列出的另一組函數(shù)用于設(shè)置頁(yè)表項(xiàng)中各標(biāo)志的值
- mk_pte_huge( ):設(shè)置頁(yè)表項(xiàng)中的 Page Size 和 Present 標(biāo)志
- pte_wrprotect( ):清除 Read/Write 標(biāo)志
- pte_rdprotect( ):清除 User/Supervisor 標(biāo)志
- pte_exprotect( ):清除 User/Supervisor 標(biāo)志
- pte_mkwrite( ):設(shè)置 Read/Write 標(biāo)志
- pte_mkread( ):設(shè)置 User/Supervisor 標(biāo)志
- pte_mkexec( ):設(shè)置 User/Supervisor 標(biāo)志
- pte_mkclean( ):清除 Dirty 標(biāo)志
- pte_mkdirty( ):設(shè)置 Dirty 標(biāo)志
- pte_mkold( ):清除 Accessed 標(biāo)志(把此頁(yè)標(biāo)記為未訪問(wèn))
- pte_mkyoung( ):設(shè)置 Accessed 標(biāo)志(把此頁(yè)標(biāo)記為訪問(wèn)過(guò))
- pte_modify(p,v):把頁(yè)表項(xiàng) p 的所有訪問(wèn)權(quán)限設(shè)置為指定的值
- ptep_set_wrprotect():與 pte_wrprotect( ) 類似,但作用于指向頁(yè)表項(xiàng)的指針
- ptep_set_access_flags( ):如果 Dirty 標(biāo)志被設(shè)置為 1 則將頁(yè)的訪問(wèn)權(quán)設(shè)置為指定的值,并調(diào)用flush_tlb_page() 函數(shù)ptep_mkdirty()與 pte_mkdirty( ) 類似,但作用于指向頁(yè)表項(xiàng)的指針。
- ptep_test_and_clear_dirty( ):與 pte_mkclean( ) 類似,但作用于指向頁(yè)表項(xiàng)的指針并返回 Dirty 標(biāo)志的舊值
- ptep_test_and_clear_young( ):與 pte_mkold( ) 類似,但作用于指向頁(yè)表項(xiàng)的指針并返回 Accessed標(biāo)志的舊值
(3)宏函數(shù)-把一個(gè)頁(yè)地址和一組保護(hù)標(biāo)志組合成頁(yè)表項(xiàng),或者執(zhí)行相反的操作
現(xiàn)在,我們來(lái)討論下表中列出的宏,它們把一個(gè)頁(yè)地址和一組保護(hù)標(biāo)志組合成頁(yè)表項(xiàng),或者執(zhí)行相反的操作,從一個(gè)頁(yè)表項(xiàng)中提取出頁(yè)地址。請(qǐng)注意這其中的一些宏對(duì)頁(yè)的引用是通過(guò) “頁(yè)描述符”的線性地址,而不是通過(guò)該頁(yè)本身的線性地址。
- pgd_index(addr):找到線性地址 addr 對(duì)應(yīng)的的目錄項(xiàng)在頁(yè)全局目錄中的索引(相對(duì)位置)
- pgd_offset(mm, addr):接收內(nèi)存描述符地址 mm 和線性地址 addr 作為參數(shù)。這個(gè)宏產(chǎn)生地址addr 在頁(yè)全局目錄中相應(yīng)表項(xiàng)的線性地址;通過(guò)內(nèi)存描述符 mm 內(nèi)的一個(gè)指針可以找到這個(gè)頁(yè)全局目錄pgd_offset_k(addr)產(chǎn)生主內(nèi)核頁(yè)全局目錄中的某個(gè)項(xiàng)的線性地址,該項(xiàng)對(duì)應(yīng)于地址
- addrpgd_page(pgd):通過(guò)頁(yè)全局目錄項(xiàng) pgd 產(chǎn)生頁(yè)上級(jí)目錄所在頁(yè)框的頁(yè)描述符地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,該宏等價(jià)于 pud_page() ,后者應(yīng)用于頁(yè)上級(jí)目錄項(xiàng)
- pud_offset(pgd, addr):參數(shù)為指向頁(yè)全局目錄項(xiàng)的指針 pgd 和線性地址 addr 。這個(gè)宏產(chǎn)生頁(yè)上級(jí)目錄中目錄項(xiàng) addr 對(duì)應(yīng)的線性地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,該宏產(chǎn)生 pgd ,即一個(gè)頁(yè)全局目錄項(xiàng)的地址
- pud_page(pud):通過(guò)頁(yè)上級(jí)目錄項(xiàng) pud 產(chǎn)生相應(yīng)的頁(yè)中間目錄的線性地址。在兩級(jí)分頁(yè)系統(tǒng)中,該宏等價(jià)于 pmd_page() ,后者應(yīng)用于頁(yè)中間目錄項(xiàng)
- pmd_index(addr):產(chǎn)生線性地址 addr 在頁(yè)中間目錄中所對(duì)應(yīng)目錄項(xiàng)的索引(相對(duì)位置)
- pmd_offset(pud, addr):接收指向頁(yè)上級(jí)目錄項(xiàng)的指針 pud 和線性地址 addr 作為參數(shù)。這個(gè)宏產(chǎn)生目錄項(xiàng) addr 在頁(yè)中間目錄中的偏移地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,它產(chǎn)生 pud ,即頁(yè)全局目錄項(xiàng)的地址
- pmd_page(pmd):通過(guò)頁(yè)中間目錄項(xiàng) pmd 產(chǎn)生相應(yīng)頁(yè)表的頁(yè)描述符地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中, pmd 實(shí)際上是頁(yè)全局目錄中的一項(xiàng)mk_pte(p,prot)接收頁(yè)描述符地址 p 和一組訪問(wèn)權(quán)限 prot 作為參數(shù),并創(chuàng)建相應(yīng)的頁(yè)表項(xiàng)
- pte_index(addr):產(chǎn)生線性地址 addr 對(duì)應(yīng)的表項(xiàng)在頁(yè)表中的索引(相對(duì)位置)
- pte_offset_kernel(dir,addr):線性地址 addr 在頁(yè)中間目錄 dir 中有一個(gè)對(duì)應(yīng)的項(xiàng),該宏就產(chǎn)生這個(gè)對(duì)應(yīng)項(xiàng),即頁(yè)表的線性地址。另外,該宏只在主內(nèi)核頁(yè)表上使用
- pte_offset_map(dir, addr):接收指向一個(gè)頁(yè)中間目錄項(xiàng)的指針 dir 和線性地址 addr 作為參數(shù),它產(chǎn)生與線性地址 addr 相對(duì)應(yīng)的頁(yè)表項(xiàng)的線性地址。如果頁(yè)表被保存在高端存儲(chǔ)器中,那么內(nèi)核建立一個(gè)臨時(shí)內(nèi)核映射,并用 pte_unmap 對(duì)它進(jìn)行釋放。pte_offset_map_nested 宏和 pte_unmap_nested 宏是相同的,但它們使用不同的臨時(shí)內(nèi)核映射
- pte_page( x ):返回頁(yè)表項(xiàng) x 所引用頁(yè)的描述符地址
- pte_to_pgoff( pte ):從一個(gè)頁(yè)表項(xiàng)的 pte 字段內(nèi)容中提取出文件偏移量,這個(gè)偏移量對(duì)應(yīng)著一個(gè)非線性文件內(nèi)存映射所在的頁(yè)
- pgoff_to_pte(offset ):為非線性文件內(nèi)存映射所在的頁(yè)創(chuàng)建對(duì)應(yīng)頁(yè)表項(xiàng)的內(nèi)容
(4)簡(jiǎn)化頁(yè)表項(xiàng)的創(chuàng)建和撤消
下面我們羅列最后一組函數(shù)來(lái)簡(jiǎn)化頁(yè)表項(xiàng)的創(chuàng)建和撤消。當(dāng)使用兩級(jí)頁(yè)表時(shí),創(chuàng)建或刪除一個(gè)頁(yè)中間目錄項(xiàng)是不重要的。如本節(jié)前部分所述,頁(yè)中間目錄僅含有一個(gè)指向下屬頁(yè)表的目錄項(xiàng)。所以,頁(yè)中間目錄項(xiàng)只是頁(yè)全局目錄中的一項(xiàng)而已。然而當(dāng)處理頁(yè)表時(shí),創(chuàng)建一個(gè)頁(yè)表項(xiàng)可能很復(fù)雜,因?yàn)榘?yè)表項(xiàng)的那個(gè)頁(yè)表可能就不存在。在這樣的情況下,有必要分配一個(gè)新頁(yè)框,把它填寫(xiě)為 0 ,并把這個(gè)表項(xiàng)加入。
如果 PAE 被激活,內(nèi)核使用三級(jí)頁(yè)表。當(dāng)內(nèi)核創(chuàng)建一個(gè)新的頁(yè)全局目錄時(shí),同時(shí)也分配四個(gè)相應(yīng)的頁(yè)中間目錄;只有當(dāng)父頁(yè)全局目錄被釋放時(shí),這四個(gè)頁(yè)中間目錄才得以釋放。當(dāng)使用兩級(jí)或三級(jí)分頁(yè)時(shí),頁(yè)上級(jí)目錄項(xiàng)總是被映射為頁(yè)全局目錄中的一個(gè)單獨(dú)項(xiàng)。與以往一樣,下表中列出的函數(shù)描述是針對(duì) 80x86 構(gòu)架的。
五、線性地址轉(zhuǎn)換
5.1分頁(yè)模式下的的線性地址轉(zhuǎn)換
線性地址、頁(yè)表和頁(yè)表項(xiàng)線性地址不管系統(tǒng)采用多少級(jí)分頁(yè)模型,線性地址本質(zhì)上都是索引+偏移量的形式,甚至你可以將整個(gè)線性地址看作N+1個(gè)索引的組合,N是系統(tǒng)采用的分頁(yè)級(jí)數(shù)。在四級(jí)分頁(yè)模型下,線性地址被分為5部分,如下圖:
在線性地址中,每個(gè)頁(yè)表索引即代表線性地址在對(duì)應(yīng)級(jí)別的頁(yè)表中中關(guān)聯(lián)的頁(yè)表項(xiàng)。正是這種索引與頁(yè)表項(xiàng)的對(duì)應(yīng)關(guān)系形成了整個(gè)頁(yè)表映射機(jī)制。
(1)頁(yè)表
多個(gè)頁(yè)表項(xiàng)的集合則為頁(yè)表,一個(gè)頁(yè)表內(nèi)的所有頁(yè)表項(xiàng)是連續(xù)存放的。頁(yè)表本質(zhì)上是一堆數(shù)據(jù),因此也是以頁(yè)為單位存放在主存中的。因此,在虛擬地址轉(zhuǎn)化物理物理地址的過(guò)程中,每訪問(wèn)一級(jí)頁(yè)表就會(huì)訪問(wèn)一次內(nèi)存。
(2)頁(yè)表項(xiàng)
頁(yè)表項(xiàng)從四種頁(yè)表項(xiàng)的數(shù)據(jù)結(jié)構(gòu)可以看出,每個(gè)頁(yè)表項(xiàng)其實(shí)就是一個(gè)無(wú)符號(hào)長(zhǎng)整型數(shù)據(jù)。每個(gè)頁(yè)表項(xiàng)分兩大類信息:頁(yè)框基地址和頁(yè)的屬性信息。在x86-32體系結(jié)構(gòu)中,每個(gè)頁(yè)表項(xiàng)的結(jié)構(gòu)圖如下:
這個(gè)圖是一個(gè)通用模型,其中頁(yè)表項(xiàng)的前20位是物理頁(yè)的基地址。由于32位的系統(tǒng)采用4kb大小的 頁(yè),因此每個(gè)頁(yè)表項(xiàng)的后12位均為0。內(nèi)核將后12位充分利用,每個(gè)位都表示對(duì)應(yīng)虛擬頁(yè)的相關(guān)屬性。
不管是那一級(jí)的頁(yè)表,它的功能就是建立虛擬地址和物理地址之間的映射關(guān)系,一個(gè)頁(yè)和一個(gè)頁(yè)框之間的映射關(guān)系體現(xiàn)在頁(yè)表項(xiàng)中。上圖中的物理頁(yè)基地址是 個(gè)抽象的說(shuō)明,如果當(dāng)前的頁(yè)表項(xiàng)位于頁(yè)全局目錄中,這個(gè)物理頁(yè)基址是指頁(yè)上級(jí)目錄所在物理頁(yè)的基地址;如果當(dāng)前頁(yè)表項(xiàng)位于頁(yè)表中,這個(gè)物理頁(yè)基地址是指最 終要訪問(wèn)數(shù)據(jù)所在物理頁(yè)的基地址。
(3)地址轉(zhuǎn)換過(guò)程
地址轉(zhuǎn)換過(guò)程有了上述的基本知識(shí),就很好理解四級(jí)頁(yè)表模式下如何將虛擬地址轉(zhuǎn)化為邏輯地址了。基本過(guò)程如下:
從CR3寄存器中讀取頁(yè)目錄所在物理頁(yè)面的基址(即所謂的頁(yè)目錄基址),從線性地址的第一部分獲取頁(yè)目錄項(xiàng)的索引,兩者相加得到頁(yè)目錄項(xiàng)的物理地址。
第一次讀取內(nèi)存得到pgd_t結(jié)構(gòu)的目錄項(xiàng),從中取出物理頁(yè)基址取出(具體位數(shù)與平臺(tái)相關(guān),如果是32系統(tǒng),則為20位),即頁(yè)上級(jí)頁(yè)目錄的物理基地址。
從線性地址的第二部分中取出頁(yè)上級(jí)目錄項(xiàng)的索引,與頁(yè)上級(jí)目錄基地址相加得到頁(yè)上級(jí)目錄項(xiàng)的物理地址。
第二次讀取內(nèi)存得到pud_t結(jié)構(gòu)的目錄項(xiàng),從中取出頁(yè)中間目錄的物理基地址。
從線性地址的第三部分中取出頁(yè)中間目錄項(xiàng)的索引,與頁(yè)中間目錄基址相加得到頁(yè)中間目錄項(xiàng)的物理地址。
第三次讀取內(nèi)存得到pmd_t結(jié)構(gòu)的目錄項(xiàng),從中取出頁(yè)表的物理基地址。
從線性地址的第四部分中取出頁(yè)表項(xiàng)的索引,與頁(yè)表基址相加得到頁(yè)表項(xiàng)的物理地址。
第四次讀取內(nèi)存得到pte_t結(jié)構(gòu)的目錄項(xiàng),從中取出物理頁(yè)的基地址。
從線性地址的第五部分中取出物理頁(yè)內(nèi)偏移量,與物理頁(yè)基址相加得到最終的物理地址。
第五次讀取內(nèi)存得到最終要訪問(wèn)的數(shù)據(jù)。
整個(gè)過(guò)程是比較機(jī)械的,每次轉(zhuǎn)換先獲取物理頁(yè)基地址,再?gòu)木€性地址中獲取索引,合成物理地址后再訪問(wèn)內(nèi)存。不管是頁(yè)表還是要訪問(wèn)的數(shù)據(jù)都是以頁(yè)為單 位存放在主存中的,因此每次訪問(wèn)內(nèi)存時(shí)都要先獲得基址,再通過(guò)索引(或偏移)在頁(yè)內(nèi)訪問(wèn)數(shù)據(jù),因此可以將線性地址看作是若干個(gè)索引的集合。
5.2 Linux中通過(guò)4級(jí)頁(yè)表訪問(wèn)物理內(nèi)存
linux中每個(gè)進(jìn)程有它自己的PGD( Page Global Directory),它是一個(gè)物理頁(yè),并包含一個(gè)pgd_t數(shù)組。
進(jìn)程的pgd_t數(shù)據(jù)見(jiàn) task_struct -> mm_struct -> pgd_t * pgd;
PTEs, PMDs和PGDs分別由pte_t, pmd_t 和pgd_t來(lái)描述。為了存儲(chǔ)保護(hù)位,pgprot_t被定義,它擁有相關(guān)的flags并經(jīng)常被存儲(chǔ)在page table entry低位(lower bits),其具體的存儲(chǔ)方式依賴于CPU架構(gòu)。
前面我們講了頁(yè)表處理的大多數(shù)函數(shù)信息,在上面我們又講了線性地址如何轉(zhuǎn)換為物理地址,其實(shí)就是不斷索引的過(guò)程。
通過(guò)如下幾個(gè)函數(shù),不斷向下索引,就可以從進(jìn)程的頁(yè)表中搜索特定地址對(duì)應(yīng)的頁(yè)面對(duì)象:
- pgd_offset根據(jù)當(dāng)前虛擬地址和當(dāng)前進(jìn)程的mm_struct獲取pgd項(xiàng)
- pud_offset參數(shù)為指向頁(yè)全局目錄項(xiàng)的指針 pgd 和線性地址 addr 。這個(gè)宏產(chǎn)生頁(yè)上級(jí)目錄中目錄項(xiàng) addr 對(duì)應(yīng)的線性地址。在兩級(jí)或三級(jí)分頁(yè)系統(tǒng)中,該宏產(chǎn)生 pgd ,即一個(gè)頁(yè)全局目錄項(xiàng)的地址
- pmd_offset根據(jù)通過(guò)pgd_offset獲取的pgd 項(xiàng)和虛擬地址,獲取相關(guān)的pmd項(xiàng)(即pte表的起始地址)
- pte_offset根據(jù)通過(guò)pmd_offset獲取的pmd項(xiàng)和虛擬地址,獲取相關(guān)的pte項(xiàng)(即物理頁(yè)的起始地址)
根據(jù)虛擬地址獲取物理頁(yè)的示例代碼詳見(jiàn)mm/memory.c中的函數(shù)follow_page
不同的版本可能有所不同,早起內(nèi)核中存在follow_page,而后來(lái)的內(nèi)核中被follow_page_mask替代,目前最新的發(fā)布4.4中為查找到此函數(shù)
我們從早期的linux-3.8的源代碼中, 截取的代碼如下
/**
* follow_page - look up a page descriptor from a user-virtual address
* @vma: vm_area_struct mapping @address
* @address: virtual address to look up
* @flags: flags modifying lookup behaviour
*
* @flags can have FOLL_ flags set, defined in <linux/mm.h>
*
* Returns the mapped (struct page *), %NULL if no mapping exists, or
* an error pointer if there is a mapping to something not represented
* by a page descriptor (see also vm_normal_page()).
*/
struct page *follow_page(struct vm_area_struct *vma, unsigned long address,
unsigned int flags)
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *ptep, pte;
spinlock_t *ptl;
struct page *page;
struct mm_struct *mm = vma->vm_mm;
page = follow_huge_addr(mm, address, flags & FOLL_WRITE);
if (!IS_ERR(page)) {
BUG_ON(flags & FOLL_GET);
goto out;
}
page = NULL;
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
goto no_page_table;
pud = pud_offset(pgd, address);
if (pud_none(*pud))
goto no_page_table;
if (pud_huge(*pud) && vma->vm_flags & VM_HUGETLB) {
BUG_ON(flags & FOLL_GET);
page = follow_huge_pud(mm, address, pud, flags & FOLL_WRITE);
goto out;
}
if (unlikely(pud_bad(*pud)))
goto no_page_table;
pmd = pmd_offset(pud, address);
if (pmd_none(*pmd))
goto no_page_table;
if (pmd_huge(*pmd) && vma->vm_flags & VM_HUGETLB) {
BUG_ON(flags & FOLL_GET);
page = follow_huge_pmd(mm, address, pmd, flags & FOLL_WRITE);
goto out;
}
if (pmd_trans_huge(*pmd)) {
if (flags & FOLL_SPLIT) {
split_huge_page_pmd(mm, pmd);
goto split_fallthrough;
}
spin_lock(&mm->page_table_lock);
if (likely(pmd_trans_huge(*pmd))) {
if (unlikely(pmd_trans_splitting(*pmd))) {
spin_unlock(&mm->page_table_lock);
wait_split_huge_page(vma->anon_vma, pmd);
} else {
page = follow_trans_huge_pmd(mm, address,
pmd, flags);
spin_unlock(&mm->page_table_lock);
goto out;
}
} else
spin_unlock(&mm->page_table_lock);
/* fall through */
}
split_fallthrough:
if (unlikely(pmd_bad(*pmd)))
goto no_page_table;
ptep = pte_offset_map_lock(mm, pmd, address, &ptl);
pte = *ptep;
if (!pte_present(pte))
goto no_page;
if ((flags & FOLL_WRITE) && !pte_write(pte))
goto unlock;
page = vm_normal_page(vma, address, pte);
if (unlikely(!page)) {
if ((flags & FOLL_DUMP) ||
!is_zero_pfn(pte_pfn(pte)))
goto bad_page;
page = pte_page(pte);
}
if (flags & FOLL_GET)
get_page(page);
if (flags & FOLL_TOUCH) {
if ((flags & FOLL_WRITE) &&
!pte_dirty(pte) && !PageDirty(page))
set_page_dirty(page);
/*
* pte_mkyoung() would be more correct here, but atomic care
* is needed to avoid losing the dirty bit: it is easier to use
* mark_page_accessed().
*/
mark_page_accessed(page);
}
if ((flags & FOLL_MLOCK) && (vma->vm_flags & VM_LOCKED)) {
/*
* The preliminary mapping check is mainly to avoid the
* pointless overhead of lock_page on the ZERO_PAGE
* which might bounce very badly if there is contention.
*
* If the page is already locked, we don't need to
* handle it now - vmscan will handle it later if and
* when it attempts to reclaim the page.
*/
if (page->mapping && trylock_page(page)) {
lru_add_drain(); /* push cached pages to LRU */
/*
* Because we lock page here and migration is
* blocked by the pte's page reference, we need
* only check for file-cache page truncation.
*/
if (page->mapping)
mlock_vma_page(page);
unlock_page(page);
}
}
unlock:
pte_unmap_unlock(ptep, ptl);
out:
return page;
bad_page:
pte_unmap_unlock(ptep, ptl);
return ERR_PTR(-EFAULT);
no_page:
pte_unmap_unlock(ptep, ptl);
if (!pte_none(pte))
return page;
no_page_table:
/*
* When core dumping an enormous anonymous area that nobody
* has touched so far, we don't want to allocate unnecessary pages or
* page tables. Return error instead of NULL to skip handle_mm_fault,
* then get_dump_page() will return NULL to leave a hole in the dump.
* But we can only make this optimization where a hole would surely
* be zero-filled if handle_mm_fault() actually did handle it.
*/
if ((flags & FOLL_DUMP) &&
(!vma->vm_ops || !vma->vm_ops->fault))
return ERR_PTR(-EFAULT);
return page;
}以上代碼可以精簡(jiǎn)為:
unsigned long v2p(int pid unsigned long va)
{
unsigned long pa = 0;
struct task_struct *pcb_tmp = NULL;
pgd_t *pgd_tmp = NULL;
pud_t *pud_tmp = NULL;
pmd_t *pmd_tmp = NULL;
pte_t *pte_tmp = NULL;
printk(KERN_INFO"PAGE_OFFSET = 0x%lx\n",PAGE_OFFSET);
printk(KERN_INFO"PGDIR_SHIFT = %d\n",PGDIR_SHIFT);
printk(KERN_INFO"PUD_SHIFT = %d\n",PUD_SHIFT);
printk(KERN_INFO"PMD_SHIFT = %d\n",PMD_SHIFT);
printk(KERN_INFO"PAGE_SHIFT = %d\n",PAGE_SHIFT);
printk(KERN_INFO"PTRS_PER_PGD = %d\n",PTRS_PER_PGD);
printk(KERN_INFO"PTRS_PER_PUD = %d\n",PTRS_PER_PUD);
printk(KERN_INFO"PTRS_PER_PMD = %d\n",PTRS_PER_PMD);
printk(KERN_INFO"PTRS_PER_PTE = %d\n",PTRS_PER_PTE);
printk(KERN_INFO"PAGE_MASK = 0x%lx\n",PAGE_MASK);
//if(!(pcb_tmp = find_task_by_pid(pid)))
if(!(pcb_tmp = findTaskByPid(pid)))
{
printk(KERN_INFO"Can't find the task %d .\n",pid);
return 0;
}
printk(KERN_INFO"pgd = 0x%p\n",pcb_tmp->mm->pgd);
/* 判斷給出的地址va是否合法(va<vm_end)*/
if(!find_vma(pcb_tmp->mm,va))
{
printk(KERN_INFO"virt_addr 0x%lx not available.\n",va);
return 0;
}
pgd_tmp = pgd_offset(pcb_tmp->mm,va);
printk(KERN_INFO"pgd_tmp = 0x%p\n",pgd_tmp);
printk(KERN_INFO"pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp));
if(pgd_none(*pgd_tmp))
{
printk(KERN_INFO"Not mapped in pgd.\n");
return 0;
}
pud_tmp = pud_offset(pgd_tmp,va);
printk(KERN_INFO"pud_tmp = 0x%p\n",pud_tmp);
printk(KERN_INFO"pud_val(*pud_tmp) = 0x%lx\n",pud_val(*pud_tmp));
if(pud_none(*pud_tmp))
{
printk(KERN_INFO"Not mapped in pud.\n");
return 0;
}
pmd_tmp = pmd_offset(pud_tmp,va);
printk(KERN_INFO"pmd_tmp = 0x%p\n",pmd_tmp);
printk(KERN_INFO"pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp));
if(pmd_none(*pmd_tmp))
{
printk(KERN_INFO"Not mapped in pmd.\n");
return 0;
}
/*在這里,把原來(lái)的pte_offset_map()改成了pte_offset_kernel*/
pte_tmp = pte_offset_kernel(pmd_tmp,va);
printk(KERN_INFO"pte_tmp = 0x%p\n",pte_tmp);
printk(KERN_INFO"pte_val(*pte_tmp) = 0x%lx\n",pte_val(*pte_tmp));
if(pte_none(*pte_tmp))
{
printk(KERN_INFO"Not mapped in pte.\n");
return 0;
}
if(!pte_present(*pte_tmp)){
printk(KERN_INFO"pte not in RAM.\n");
return 0;
}
pa = (pte_val(*pte_tmp) & PAGE_MASK) | (va & ~PAGE_MASK);
printk(KERN_INFO"virt_addr 0x%lx in RAM is 0x%lx t .\n",va,pa);
printk(KERN_INFO"contect in 0x%lx is 0x%lx\n", pa, *(unsigned long *)((char *)pa + PAGE_OFFSET)
}六、Linux 分頁(yè)機(jī)制的顯著優(yōu)勢(shì)
6.1內(nèi)存保護(hù)與隔離
在 Linux 的內(nèi)存管理體系中,分頁(yè)機(jī)制就像是一位忠誠(chéng)的衛(wèi)士,為系統(tǒng)的安全穩(wěn)定運(yùn)行保駕護(hù)航,而其中頁(yè)表項(xiàng)權(quán)限位則是這位衛(wèi)士手中的 “秘密武器”,在內(nèi)存保護(hù)和隔離方面發(fā)揮著關(guān)鍵作用。
每個(gè)進(jìn)程都擁有屬于自己的獨(dú)立頁(yè)表,這就如同每個(gè)進(jìn)程都有一個(gè)專屬的 “房間”,而頁(yè)表則是這個(gè)房間的 “門(mén)鎖” 和 “管家”。頁(yè)表項(xiàng)中的權(quán)限位,如讀(Read)權(quán)限位、寫(xiě)(Write)權(quán)限位和執(zhí)行(Execute)權(quán)限位等,就像是門(mén)鎖上的不同 “密碼”,嚴(yán)格控制著對(duì)內(nèi)存頁(yè)的訪問(wèn)權(quán)限 。例如,當(dāng)一個(gè)進(jìn)程試圖訪問(wèn)另一個(gè)進(jìn)程的內(nèi)存空間時(shí),由于其頁(yè)表中對(duì)應(yīng)的權(quán)限位不允許這樣的訪問(wèn),系統(tǒng)就會(huì)立即檢測(cè)到這種違規(guī)行為,并觸發(fā)一個(gè)頁(yè)錯(cuò)誤(Page Fault) 。這就好比有人拿著錯(cuò)誤的密碼試圖打開(kāi)別人房間的門(mén),門(mén)鎖會(huì)立刻發(fā)出警報(bào)。
操作系統(tǒng)在接收到頁(yè)錯(cuò)誤后,會(huì)迅速采取相應(yīng)措施,比如記錄錯(cuò)誤信息,終止違規(guī)進(jìn)程,以防止惡意進(jìn)程的攻擊和破壞,確保每個(gè)進(jìn)程的數(shù)據(jù)都能安全地存放在自己的 “房間” 里,實(shí)現(xiàn)了不同進(jìn)程之間內(nèi)存的有效隔離 。這種基于頁(yè)表項(xiàng)權(quán)限位的內(nèi)存保護(hù)機(jī)制,為 Linux 系統(tǒng)的安全性和穩(wěn)定性提供了堅(jiān)實(shí)的保障,讓各個(gè)進(jìn)程能夠在相互隔離的環(huán)境中穩(wěn)定運(yùn)行,避免了數(shù)據(jù)泄露和非法訪問(wèn)帶來(lái)的風(fēng)險(xiǎn)。
6.2高效內(nèi)存利用
分頁(yè)機(jī)制在提高內(nèi)存利用率方面,有著出色的表現(xiàn),堪稱內(nèi)存管理的 “效率大師”。它通過(guò)將內(nèi)存劃分為固定大小的頁(yè),巧妙地解決了內(nèi)存碎片這一棘手問(wèn)題。
在傳統(tǒng)的內(nèi)存分配方式中,由于進(jìn)程對(duì)內(nèi)存的需求大小不一,頻繁的分配和釋放內(nèi)存很容易產(chǎn)生內(nèi)存碎片。就像你有一塊大蛋糕,每次根據(jù)不同的需求切下大小不一的塊,時(shí)間久了,就會(huì)剩下很多小塊的 “邊角料”,這些 “邊角料” 因?yàn)樘《鵁o(wú)法被充分利用,造成了內(nèi)存的浪費(fèi)。而分頁(yè)機(jī)制則像是把蛋糕切成了大小相同的小塊,無(wú)論進(jìn)程需要多少內(nèi)存,都可以通過(guò)分配相應(yīng)數(shù)量的頁(yè)來(lái)滿足需求 。當(dāng)進(jìn)程結(jié)束后,釋放的頁(yè)又可以被系統(tǒng)重新分配,大大減少了內(nèi)存碎片的產(chǎn)生,提高了內(nèi)存的使用效率。
同時(shí),分頁(yè)機(jī)制還支持動(dòng)態(tài)內(nèi)存分配,能夠根據(jù)進(jìn)程的實(shí)際需求,靈活地分配和回收內(nèi)存頁(yè)。當(dāng)一個(gè)進(jìn)程需要更多的內(nèi)存時(shí),系統(tǒng)可以迅速為其分配新的頁(yè);當(dāng)進(jìn)程不再需要某些內(nèi)存時(shí),這些頁(yè)又能及時(shí)被回收,重新投入到其他需要的地方。這種動(dòng)態(tài)分配的方式,就像一個(gè)智能的資源分配器,能夠根據(jù)實(shí)際情況,合理地調(diào)配內(nèi)存資源,避免了內(nèi)存的閑置和浪費(fèi),使得系統(tǒng)能夠在有限的內(nèi)存條件下,高效地運(yùn)行多個(gè)進(jìn)程 。
例如,在一個(gè)同時(shí)運(yùn)行多個(gè)程序的 Linux 系統(tǒng)中,分頁(yè)機(jī)制可以根據(jù)每個(gè)程序的實(shí)時(shí)內(nèi)存需求,動(dòng)態(tài)地分配和回收內(nèi)存頁(yè),確保每個(gè)程序都能獲得足夠的內(nèi)存資源,同時(shí)又不會(huì)造成內(nèi)存的過(guò)度占用,極大地提高了內(nèi)存的利用率,提升了系統(tǒng)的整體性能。
七、分頁(yè)機(jī)制的應(yīng)用場(chǎng)景
7.1服務(wù)器場(chǎng)景
在服務(wù)器環(huán)境中,通常會(huì)同時(shí)運(yùn)行多個(gè)不同的進(jìn)程,這些進(jìn)程各自承擔(dān)著不同的任務(wù),比如 Web 服務(wù)器進(jìn)程負(fù)責(zé)處理網(wǎng)頁(yè)請(qǐng)求,數(shù)據(jù)庫(kù)服務(wù)器進(jìn)程負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和讀取,郵件服務(wù)器進(jìn)程負(fù)責(zé)郵件的收發(fā)等 。以一個(gè)典型的電商網(wǎng)站服務(wù)器為例,它可能同時(shí)運(yùn)行著 Web 服務(wù)進(jìn)程、數(shù)據(jù)庫(kù)服務(wù)進(jìn)程和緩存服務(wù)進(jìn)程等。Web 服務(wù)進(jìn)程需要處理大量用戶的頁(yè)面訪問(wèn)請(qǐng)求,數(shù)據(jù)庫(kù)服務(wù)進(jìn)程則要頻繁地進(jìn)行數(shù)據(jù)的查詢和更新操作,緩存服務(wù)進(jìn)程則負(fù)責(zé)存儲(chǔ)和管理常用的數(shù)據(jù),以提高訪問(wèn)速度 。
在這種多進(jìn)程并發(fā)運(yùn)行的情況下,內(nèi)存資源的管理變得至關(guān)重要。分頁(yè)機(jī)制就像是一位高效的資源調(diào)度員,為每個(gè)進(jìn)程分配獨(dú)立的虛擬內(nèi)存空間,確保它們?cè)诟髯缘?“小天地” 里運(yùn)行,互不干擾。當(dāng) Web 服務(wù)進(jìn)程收到用戶的頁(yè)面請(qǐng)求時(shí),它會(huì)在自己的虛擬內(nèi)存空間中查找和處理相關(guān)數(shù)據(jù),而不會(huì)影響到數(shù)據(jù)庫(kù)服務(wù)進(jìn)程和緩存服務(wù)進(jìn)程的正常運(yùn)行 。同時(shí),分頁(yè)機(jī)制通過(guò)頁(yè)表的映射,能夠快速地將虛擬地址轉(zhuǎn)換為物理地址,提高內(nèi)存訪問(wèn)的效率。當(dāng)數(shù)據(jù)庫(kù)服務(wù)進(jìn)程需要讀取或?qū)懭霐?shù)據(jù)時(shí),分頁(yè)機(jī)制可以迅速地定位到物理內(nèi)存中的數(shù)據(jù)位置,減少數(shù)據(jù)訪問(wèn)的延遲,保證數(shù)據(jù)庫(kù)操作的高效性 。
而且,分頁(yè)機(jī)制還能根據(jù)進(jìn)程的實(shí)際需求,動(dòng)態(tài)地分配和回收內(nèi)存頁(yè)。當(dāng)電商網(wǎng)站在促銷(xiāo)活動(dòng)期間,訪問(wèn)量大幅增加,Web 服務(wù)進(jìn)程需要更多的內(nèi)存來(lái)處理請(qǐng)求時(shí),分頁(yè)機(jī)制可以及時(shí)為其分配額外的內(nèi)存頁(yè);當(dāng)活動(dòng)結(jié)束后,訪問(wèn)量減少,分頁(yè)機(jī)制又能回收多余的內(nèi)存頁(yè),將其重新分配給其他有需要的進(jìn)程,從而實(shí)現(xiàn)內(nèi)存資源的高效利用,保障服務(wù)器在高負(fù)載情況下的穩(wěn)定運(yùn)行 。
7.2虛擬化場(chǎng)景
在虛擬化技術(shù)中,分頁(yè)機(jī)制扮演著不可或缺的角色,它是實(shí)現(xiàn)虛擬機(jī)內(nèi)存隔離與分配的關(guān)鍵所在。以 KVM(Kernel-based Virtual Machine)虛擬化技術(shù)為例,它基于 Linux 內(nèi)核,通過(guò)將 Linux 內(nèi)核轉(zhuǎn)變?yōu)橐粋€(gè) Hypervisor,能夠在同一臺(tái)物理主機(jī)上運(yùn)行多個(gè)虛擬機(jī) 。
每個(gè)虛擬機(jī)都仿佛是一臺(tái)獨(dú)立的物理計(jì)算機(jī),擁有自己獨(dú)立的操作系統(tǒng)和應(yīng)用程序,而分頁(yè)機(jī)制則為這些虛擬機(jī)提供了獨(dú)立的內(nèi)存空間,就像為每個(gè)虛擬機(jī)劃分了專屬的 “內(nèi)存區(qū)域”,確保它們之間的內(nèi)存相互隔離,不會(huì)出現(xiàn)數(shù)據(jù)泄露或相互干擾的情況。當(dāng)一個(gè)虛擬機(jī)中的應(yīng)用程序訪問(wèn)內(nèi)存時(shí),分頁(yè)機(jī)制會(huì)根據(jù)該虛擬機(jī)的頁(yè)表,將虛擬地址準(zhǔn)確地映射到對(duì)應(yīng)的物理內(nèi)存頁(yè)上,保證虛擬機(jī)的正常運(yùn)行 。
同時(shí),分頁(yè)機(jī)制還能實(shí)現(xiàn)內(nèi)存的高效分配。在物理主機(jī)內(nèi)存有限的情況下,分頁(yè)機(jī)制可以根據(jù)各個(gè)虛擬機(jī)的實(shí)際內(nèi)存需求,動(dòng)態(tài)地分配內(nèi)存頁(yè)。比如,當(dāng)一臺(tái)虛擬機(jī)運(yùn)行一個(gè)輕量級(jí)的應(yīng)用程序時(shí),它可能只需要較少的內(nèi)存,分頁(yè)機(jī)制就會(huì)為其分配適量的內(nèi)存頁(yè);而當(dāng)另一臺(tái)虛擬機(jī)運(yùn)行一個(gè)大型數(shù)據(jù)庫(kù)應(yīng)用程序時(shí),它需要大量的內(nèi)存,分頁(yè)機(jī)制會(huì)優(yōu)先滿足其需求,為其分配足夠的內(nèi)存頁(yè) 。
這種靈活的內(nèi)存分配方式,使得物理主機(jī)的內(nèi)存資源能夠得到充分利用,提高了虛擬化環(huán)境的整體性能 。此外,分頁(yè)機(jī)制還能支持內(nèi)存的共享。在一些情況下,多個(gè)虛擬機(jī)可能會(huì)運(yùn)行相同的操作系統(tǒng)或應(yīng)用程序,分頁(yè)機(jī)制可以讓這些虛擬機(jī)共享相同的物理內(nèi)存頁(yè),減少內(nèi)存的占用,進(jìn)一步提高內(nèi)存的利用率 。例如,在一個(gè)云計(jì)算數(shù)據(jù)中心中,通過(guò) KVM 虛擬化技術(shù)運(yùn)行著大量的虛擬機(jī),分頁(yè)機(jī)制的存在使得這些虛擬機(jī)能夠高效地共享物理主機(jī)的內(nèi)存資源,實(shí)現(xiàn)了資源的最大化利用,為用戶提供了穩(wěn)定、高效的云計(jì)算服務(wù) 。




























