講解linux內(nèi)存管理-內(nèi)存管理架構
一、內(nèi)存管理架構
內(nèi)存管理子系統(tǒng)架構可以分為:用戶空間、內(nèi)核空間及硬件部分3個層面,具體結構如下所示: 1、用戶空間:應用程序使用malloc()申請內(nèi)存資源/free()釋放內(nèi)存資源。 2、內(nèi)核空間:內(nèi)核總是駐留在內(nèi)存中,是操作系統(tǒng)的一部分。內(nèi)核空間為內(nèi)核保留,不允許應用程序讀寫該區(qū)域的內(nèi)容或直接調(diào)用內(nèi)核代碼定義的函數(shù)。 3、硬件:處理器包含一個內(nèi)存管理單元(Memory Management Uint,MMU)的部件,負責把虛擬地址轉換為物理地址。
二、虛擬地址空間布局架構
上面的用戶空間和內(nèi)核空間所指的都是虛擬地址,物理地址沒有用戶和內(nèi)核之分。每個項目的物理地址對于進程不可見,誰也不能直接訪問這個物理地址。操作系統(tǒng)會給進程分配一個虛擬地址。所有進程看到的這個地址都是一樣的,里面的內(nèi)存都是從 0 開始編號。 所有進程共享內(nèi)核虛擬地址空間,每個進程有獨立的用戶虛擬地址空間,同一個線程組的用戶線程共享用戶虛擬地址空間,內(nèi)核線程沒有用戶虛擬地址空間。 在程序里面,指令寫入的地址是虛擬地址。例如,位置為 10M 的內(nèi)存區(qū)域,操作系統(tǒng)會提供一種機制,將不同進程的虛擬地址和不同內(nèi)存的物理地址映射起來。 當程序要訪問虛擬地址的時候,由內(nèi)核的數(shù)據(jù)結構進行轉換,轉換成不同的物理地址,這樣不同的進程運行的時候,寫入的是不同的物理地址,這樣就不會沖突了。 32位處理器使用32位虛擬地址,而64位處理器卻不是使用64位虛擬地址。因為目前應用程序沒有那么大的內(nèi)存需求,所以ARM64和X86_64處理器不支持完全的64位虛擬地址,而是使用了48位。我們計算一下,如果是 32 位,有 2^32 = 4G 的內(nèi)存空間都是我的,不管內(nèi)存是不是真的有 4G。如果是 64 位,在 ARM64和X86_64 下面,其實只使用了 48 位,那也挺恐怖的。48 位地址長度也就是對應了 256TB 的地址空間。我都沒怎么見過 256T 的硬盤,別說是內(nèi)存了。

這么大的虛擬空間一切二,一部分用來放內(nèi)核的東西,稱為內(nèi)核空間,一部分用來放進程的東西,稱為用戶空間。 用戶空間其實包含以下幾個區(qū)域,我們最低位開始排起: 1.代碼段,數(shù)據(jù)段,未初始化的數(shù)據(jù)段(bss) 2.存放動態(tài)生成數(shù)據(jù)的堆,堆是往高地址增長的 3.動態(tài)庫的代碼段,數(shù)據(jù)段和未初始化的數(shù)據(jù)段(bss) 4.存放局部變量和實現(xiàn)函數(shù)調(diào)用的棧 5.把文件映射到虛擬地址空間的內(nèi)存映射區(qū) 6.存放在棧底的環(huán)境變量和參數(shù)字符串
【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


三、物理內(nèi)存體系架構
目前多處理器系統(tǒng)有兩種體系結構:
1)一致內(nèi)存訪問(Uniform Memory Access,UMA),所有處理器訪問內(nèi)存花費的時間是相同。

這種結構的CPU 是通過一條通用總線連接到北橋,北橋中的內(nèi)存控制器鏈接著內(nèi)存。這種設計中,瓶頸馬上出現(xiàn)了。第一個瓶頸與設備對RAM 的訪問有關。早期,所有設備之間的通信都需要經(jīng)過 CPU,結果嚴重影響了整個系統(tǒng)的性能。為了解決這個問題,有些設備加入了直接內(nèi)存訪問(DMA)的能力。DMA 允許設備在北橋的幫助下,無需 CPU 的干涉,直接讀寫 RAM。到了今天,所有高性能的設備都可以使用 DMA。雖然 DMA 大大降低了 CPU 的負擔,卻占用了北橋的帶寬,與 CPU 形成了爭用。所以現(xiàn)在很少使用了。
2)非一致內(nèi)存訪問(Non-Unit Memory Access,NUMA):指內(nèi)存被劃分成多個內(nèi)存節(jié)點的多處理器系統(tǒng),訪問一個內(nèi)存節(jié)點花費的時間取決于處理器和內(nèi)存節(jié)點的距離。

采用這樣的架構,系統(tǒng)里有幾個處理器,就可以有幾個內(nèi)存庫。系統(tǒng)仍然要讓所有內(nèi)存能被所有處理器所訪問,導致內(nèi)存不再是統(tǒng)一的資源。處理器能以正常的速度訪問本地內(nèi)存(連接到該處理器的內(nèi)存)。但它訪問其它處理器的內(nèi)存時,卻需要使用處理器之間的互聯(lián)通道。 使用下面的命令可以查看內(nèi)存架構:
如上面所示,我們只有一個內(nèi)存節(jié)點,我們這個8個cpu核心都是使用這一個內(nèi)存節(jié)點,所以我們可以認為我們是UMA架構的。
四、內(nèi)存結構
由于現(xiàn)在我接觸的基本都是使用UMA的結構,所以下面說的都是這種: 內(nèi)存管理子系統(tǒng)使用節(jié)點(node),區(qū)域(zone)、頁(page)三級結構描述物理內(nèi)存。節(jié)點是基于哪個cpu,一般多少核的cpu就有多少個節(jié)點node;zone是每個cpu,也就是每個節(jié)點會把內(nèi)存分為高端內(nèi)存,低端內(nèi)存,DMA區(qū)域等等的內(nèi)存區(qū)域;頁就是物理內(nèi)存的最小單位了,也是虛擬內(nèi)存映射到物理內(nèi)存的最小單位。最后,在NUMA內(nèi)存架構中, Linux定義了一個 pglist_data 的結構體來管理所有的內(nèi)存節(jié)點.
1.內(nèi)存節(jié)點(node)
在NUMA體系的內(nèi)存節(jié)點是根據(jù)處理器和內(nèi)存的距離劃分的,而在具有不連續(xù)內(nèi)存的NUMA系統(tǒng)中,表示比區(qū)域的級別更高的內(nèi)存區(qū)域,根據(jù)物理地址是否連續(xù)劃分,每塊物理地址連續(xù)的內(nèi)存是一個內(nèi)存節(jié)點。內(nèi)存節(jié)點結構體在linux內(nèi)核include/linux/mmzone.h文件中,
2.內(nèi)存區(qū)域(zone)
每一個節(jié)點分成一個個區(qū)域 zone,放在數(shù)組 node_zones 里面。這個數(shù)組的大小為 MAX_NR_ZONES。我們來看區(qū)域的定義。內(nèi)存節(jié)點被劃分為內(nèi)存區(qū)域,內(nèi)存區(qū)域結構體在linux內(nèi)核include/linux/mmzone.h文件中
ZONE_DMA 是指可用于作 DMA(Direct Memory Access,直接內(nèi)存存取)的內(nèi)存。DMA 是這樣一種機制:要把外設的數(shù)據(jù)讀入內(nèi)存或把內(nèi)存的數(shù)據(jù)傳送到外設,原來都要通過 CPU 控制完成,但是這會占用 CPU,影響 CPU 處理其他事情,所以有了 DMA 模式。CPU 只需向 DMA 控制器下達指令,讓 DMA 控制器來處理數(shù)據(jù)的傳送,數(shù)據(jù)傳送完畢再把信息反饋給 CPU,這樣就可以解放 CPU。對于 64 位系統(tǒng),有兩個 DMA 區(qū)域。除了上面說的 ZONE_DMA,還有 ZONE_DMA32。在這里你大概理解 DMA 的原理就可以,不必糾結,我們后面會講 DMA 的機制。 ZONE_NORMAL 是直接映射區(qū),就是上一節(jié)講的,從物理內(nèi)存到虛擬內(nèi)存的內(nèi)核區(qū)域,通過加上一個常量直接映射。ZONE_HIGHMEM 是高端內(nèi)存區(qū),就是上一節(jié)講的,對于 32 位系統(tǒng)來說超過 896M 的地方,對于 64 位沒必要有的一段區(qū)域。 ZONE_MOVABLE 是可移動區(qū)域,通過將物理內(nèi)存劃分為可移動分配區(qū)域和不可移動分配區(qū)域來避免內(nèi)存碎片。 這里你需要注意一下,我們剛才對于區(qū)域的劃分,都是針對物理內(nèi)存的。
內(nèi)存頁(page) 了解了區(qū)域 zone,接下來我們就到了組成物理內(nèi)存的基本單位,頁的數(shù)據(jù)結構 struct page。這是一個特別復雜的結構,里面有很多的 union,union 結構是在 C 語言中被用于同一塊內(nèi)存根據(jù)情況保存不同類型數(shù)據(jù)的一種方式。這里之所以用了 union,是因為一個物理頁面使用模式有兩種。第一種模式,僅需分配小塊內(nèi)存,Linux 系統(tǒng)采用了一種被稱為 slab allocator的技術,下一節(jié)會講。 第二種模式,要用就用一整頁。這一整頁的內(nèi)存,或者直接和虛擬地址空間建立映射關系,我們把這種稱為匿名頁(Anonymous Page)?;蛘哂糜陉P聯(lián)一個文件,然后再和虛擬地址空間建立映射關系,這樣的文件,我們稱為內(nèi)存映射文件(Memory-mapped File)。 每個物理頁對應一個page結構體,稱為頁描述符,內(nèi)存節(jié)點的pglist_data實例的成員node_mem_map指向該內(nèi)存節(jié)點包含的所有物理頁的頁描述符組成的數(shù)組。內(nèi)存區(qū)域結構體在linux內(nèi)核include/linux/mm_types.h文件中
其結構大概如下圖所示:

五、內(nèi)存模型
內(nèi)存模型是其實就是從cpu的角度看,其物理內(nèi)存的分布情況,在linux kernel中,使用什么的方式來管理這些物理內(nèi)存。 內(nèi)存管理子系統(tǒng)支持3種內(nèi)存模型: 1)平坦內(nèi)存(Flat Memory):內(nèi)存的物理地址空間是連續(xù)的,沒有空洞。 如果從系統(tǒng)中任意一個processor的角度來看,當它訪問物理內(nèi)存的時候,物理地址空間是一個連續(xù)的,沒有空洞的地址空間,那么這種計算機系統(tǒng)的內(nèi)存模型就是Flat memory。這種內(nèi)存模型下,物理內(nèi)存的管理比較簡單,每一個物理頁幀都會有一個page數(shù)據(jù)結構來抽象,因此系統(tǒng)中存在一個struct page的數(shù)組(mem_map),每一個數(shù)組條目指向一個實際的物理頁幀(page frame)。在flat memory的情況下,PFN(page frame number)和mem_map數(shù)組index的關系是線性的(有一個固定偏移,如果內(nèi)存對應的物理地址等于0,那么PFN就是數(shù)組index)。因此從PFN到對應的page數(shù)據(jù)結構是非常容易的,反之亦然,具體可以參考page_to_pfn和pfn_to_page的定義。此外,對于flat memory model,節(jié)點(struct pglist_data)只有一個(為了和Discontiguous Memory Model采用同樣的機制)。需要強調(diào)的是struct page所占用的內(nèi)存位于直接映射(directly mapped)區(qū)間,因此操作系統(tǒng)不需要再為其建立page table。
2)不連續(xù)內(nèi)存(Discontiguous Memory):內(nèi)存的物理地址空間存在空洞,這種模型可以高效地處理空洞。 如果cpu在訪問物理內(nèi)存的時候,其地址空間有一些空洞,是不連續(xù)的,那么這種計算機系統(tǒng)的內(nèi)存模型就是Discontiguous memory。一般而言,NUMA架構的計算機系統(tǒng)的memory model都是選擇Discontiguous Memory,不過,這兩個概念其實是不同的。NUMA強調(diào)的是memory和processor的位置關系,和內(nèi)存模型其實是沒有關系的,只不過,由于同一node上的memory和processor有更緊密的耦合關系(訪問更快),因此需要多個node來管理。Discontiguous memory本質(zhì)上是flat memory內(nèi)存模型的擴展,整個物理內(nèi)存的address space大部分是成片的大塊內(nèi)存,中間會有一些空洞,每一個成片的memory address space屬于一個node(如果局限在一個node內(nèi)部,其內(nèi)存模型是flat memory)。因此,這種內(nèi)存模型下,節(jié)點數(shù)據(jù)(struct pglist_data)有多個,宏定義NODE_DATA可以得到指定節(jié)點的struct pglist_data。而,每個節(jié)點管理的物理內(nèi)存保存在struct pglist_data 數(shù)據(jù)結構的node_mem_map成員中(概念類似flat memory中的mem_map)。這時候,從PFN轉換到具體的struct page會稍微復雜一點,我們首先要從PFN得到node ID,然后根據(jù)這個ID找到對于的pglist_data 數(shù)據(jù)結構,也就找到了對應的page數(shù)組,之后的方法就類似flat memory了。
3)稀疏內(nèi)存(Space Memory):內(nèi)存的物理地址空間存在空洞,如果要支持內(nèi)存熱插拔,只能選擇稀疏內(nèi)存模型。 Memory model也是一個演進過程,剛開始的時候,使用flat memory去抽象一個連續(xù)的內(nèi)存地址空間(mem_maps[]),出現(xiàn)NUMA之后,整個不連續(xù)的內(nèi)存空間被分成若干個node,每個node上是連續(xù)的內(nèi)存地址空間,也就是說,原來的單一的一個mem_maps[]變成了若干個mem_maps[]了。一切看起來已經(jīng)完美了,但是memory hotplug的出現(xiàn)讓原來完美的設計變得不完美了,因為即便是一個node中的mem_maps[]也有可能是不連續(xù)了。其實,在出現(xiàn)了sparse memory之后,Discontiguous memory內(nèi)存模型已經(jīng)不是那么重要了,按理說sparse memory最終可以替代Discontiguous memory的,這個替代過程正在進行中,4.4的內(nèi)核仍然是有3中內(nèi)存模型可以選擇。 為什么說sparse memory最終可以替代Discontiguous memory呢?實際上在sparse memory內(nèi)存模型下,連續(xù)的地址空間按照SECTION(例如1G)被分成了一段一段的,其中每一section都是hotplug的,因此sparse memory下,內(nèi)存地址空間可以被切分的更細,支持更離散的Discontiguous memory。此外,在sparse memory沒有出現(xiàn)之前,NUMA和Discontiguous memory總是剪不斷,理還亂的關系:NUMA并沒有規(guī)定其內(nèi)存的連續(xù)性,而Discontiguous memory系統(tǒng)也并非一定是NUMA系統(tǒng),但是這兩種配置都是multi node的。有了sparse memory之后,我們終于可以把內(nèi)存的連續(xù)性和NUMA的概念剝離開來:一個NUMA系統(tǒng)可以是flat memory,也可以是sparse memory,而一個sparse memory系統(tǒng)可以是NUMA,也可以是UMA的。對于經(jīng)典的sparse memory模型,一個section的struct page數(shù)組所占用的內(nèi)存來自directly mapped區(qū)域,頁表在初始化的時候就建立好了,分配了page frame也就是分配了虛擬地址。但是,對于SPARSEMEM_VMEMMAP而言,虛擬地址一開始就分配好了,是vmemmap開始的一段連續(xù)的虛擬地址空間,每一個page都有一個對應的struct page,當然,只有虛擬地址,沒有物理地址。因此,當一個section被發(fā)現(xiàn)后,可以立刻找到對應的struct page的虛擬地址,當然,還需要分配一個物理的page frame,然后建立頁表什么的,因此,對于這種sparse memory,開銷會稍微大一些(多了個建立映射的過程)。
六、虛擬地址和物理地址的轉換
cpu讀寫指令和數(shù)據(jù)都需要用到內(nèi)存,而我們程序操作的都是虛擬內(nèi)存,大家覺得,為什么系統(tǒng)設計者要引入虛擬地址呢? 設想一下,如果一臺計算機的內(nèi)存中只運行一個程序 A,因為程序 A 的地址在鏈接時就可以確定,例如從內(nèi)存地址 0x8000 開始,每次運行程序 A 都裝入內(nèi)存 0x8000 地址處開始運行,沒有其它程序干擾?,F(xiàn)在改變一下,內(nèi)存中又放一道程序 B,程序 A 和程序 B 各自運行一秒鐘,如此循環(huán),直到其中之一結束。這個新場景下就會產(chǎn)生一些問題,當然這里我們只關心內(nèi)存相關的這幾個核心問題。
誰來保證程序 A 跟程序 B 沒有內(nèi)存地址的沖突?換句話說,就是程序 A、B 各自放在什么內(nèi)存地址,這個問題是由 A、B 程序協(xié)商,還是由操作系統(tǒng)決定。 怎樣保證程序 A 跟程序 B 不會互相讀寫各自的內(nèi)存空間?這個問題相對簡單,用保護模式就能解決。 如何解決內(nèi)存容量問題?程序 A 和程序 B,在不斷開發(fā)迭代中程序代碼占用的空間會越來越大,導致內(nèi)存裝不下。 還要考慮一個擴展后的復雜情況,如果不只程序 A、B,還可能有程序 C、D、E、F、G……它們分別由不同的公司開發(fā),而每臺計算機的內(nèi)存容量不同。這時候,又對我們的內(nèi)存方案有怎樣的影響呢? 想完美地解決以上最核心的 4 個問題,一個較好的方案是:讓所有的程序都各自享有一個從 0 開始到最大地址的空間,這個地址空間是獨立的,是該程序私有的,其它程序既看不到,也不能訪問該地址空間,這個地址空間和其它程序無關,和具體的計算機也無關。事實上,計算機科學家們早就這么做了,這個方案就是虛擬地址。 虛擬地址 正如其名,這個地址是虛擬的,自然而然地和具體環(huán)境進行了解耦,這個環(huán)境包括系統(tǒng)軟件環(huán)境和硬件環(huán)境。 事實上,所有的應用程序開始的部分都是這樣的。這正是因為每個應用程序的虛擬地址空間都是相同且獨立的。那么這個地址是由誰產(chǎn)生的呢?答案是鏈接器,其實我們開發(fā)軟件經(jīng)過編譯步驟后,就需要鏈接成可執(zhí)行文件才可以運行,而鏈接器的主要工作就是把多個代碼模塊組裝在一起,并解決模塊之間的引用,即處理程序代碼間的地址引用,形成程序運行的靜態(tài)內(nèi)存空間視圖。只不過這個地址是虛擬而統(tǒng)一的,而根據(jù)操作系統(tǒng)的不同,這個虛擬地址空間的定義也許不同,應用軟件開發(fā)人員無需關心,由開發(fā)工具鏈給自動處理了。由于這虛擬地址是獨立且統(tǒng)一的,所以各個公司開發(fā)的各個應用完全不用擔心自己的內(nèi)存空間被占用和改寫。 物理地址 雖然虛擬地址解決了很多問題,但是虛擬地址只是邏輯上存在的地址,無法作用于硬件電路的,程序裝進內(nèi)存中想要執(zhí)行,就需要和內(nèi)存打交道,從內(nèi)存中取得指令和數(shù)據(jù)。而內(nèi)存只認一種地址,那就是物理地址。 什么是物理地址呢?物理地址在邏輯上也是一個數(shù)據(jù),只不過這個數(shù)據(jù)會被地址譯碼器等電子器件變成電子信號,放在地址總線上,地址總線電子信號的各種組合就可以選擇到內(nèi)存的儲存單元了。 但是地址總線上的信號(即物理地址),也可以選擇到別的設備中的儲存單元,如顯卡中的顯存、I/O 設備中的寄存器、網(wǎng)卡上的網(wǎng)絡幀緩存器。不過如果不做特別說明,我們說的物理地址就是指選擇內(nèi)存單元的地址。 虛擬地址到物理地址的轉換 明白了虛擬地址和物理地址之后,我們發(fā)現(xiàn)虛擬地址必須轉換成物理地址,這樣程序才能正常執(zhí)行。要轉換就必須要轉換機構,它相當于一個函數(shù):p=f(v),輸入虛擬地址 v,輸出物理地址 p。 那么要怎么實現(xiàn)這個函數(shù)呢?用軟件方式實現(xiàn)太低效,用硬件實現(xiàn)沒有靈活性,最終就用了軟硬件結合的方式實現(xiàn),它就是 MMU(內(nèi)存管理單元)。MMU 可以接受軟件給出的地址對應關系數(shù)據(jù),進行地址轉換。 MMU一個工具,我們通過mmu去讀取地址關系轉化表,再根據(jù)虛擬地址空間地址找到物理地址所在區(qū)域,可以看圖:

下面我們不妨想一想地址關系轉換表的實現(xiàn). 如果在地址關系轉換表中,這樣來存放:一個虛擬地址對應一個物理地址。那么問題來了,32 位地址空間下,4GB 虛擬地址的地址關系轉換表就會把整個 32 位物理地址空間用完,這顯然不行。 系統(tǒng)設計者最后采用一個這樣的方案,即把虛擬地址空間和物理地址空間都分成同等大小的塊,也稱為頁,按照虛擬頁和物理頁進行轉換。根據(jù)軟件配置不同,這個頁的大小可以設置為 4KB、2MB、4MB、1GB,這樣就進入了現(xiàn)代內(nèi)存管理模式——分頁模型。于是mmu的功能就是這樣的了:
結合圖片可以看出,一個虛擬頁可以對應到一個物理頁,由于頁大小一經(jīng)配置就是固定的,所以在地址關系轉換表中,只要存放虛擬頁地址對應的物理頁地址就行了。 MMU 頁表 現(xiàn)在我們開始研究地址關系轉換表,其實它有個更加專業(yè)的名字——頁表。它描述了虛擬地址到物理地址的轉換關系,也可以說是虛擬頁到物理頁的映射關系,所以稱為頁表。為了增加靈活性和節(jié)約物理內(nèi)存空間(因為頁表是放在物理內(nèi)存中的),所以頁表中并不存放虛擬地址和物理地址的對應關系,只存放物理頁面的地址,MMU 以虛擬地址為索引去查表返回物理頁面地址,而且頁表是分級的,總體分為三個部分:一個頂級頁目錄,多個中級頁目錄,最后才是頁表。

結合圖片可以看出,一個虛擬頁可以對應到一個物理頁,由于頁大小一經(jīng)配置就是固定的,所以在地址關系轉換表中,只要存放虛擬頁地址對應的物理頁地址就行了。 MMU 頁表 現(xiàn)在我們開始研究地址關系轉換表,其實它有個更加專業(yè)的名字——頁表。它描述了虛擬地址到物理地址的轉換關系,也可以說是虛擬頁到物理頁的映射關系,所以稱為頁表。為了增加靈活性和節(jié)約物理內(nèi)存空間(因為頁表是放在物理內(nèi)存中的),所以頁表中并不存放虛擬地址和物理地址的對應關系,只存放物理頁面的地址,MMU 以虛擬地址為索引去查表返回物理頁面地址,而且頁表是分級的,總體分為三個部分:一個頂級頁目錄,多個中級頁目錄,最后才是頁表。.

上圖中 CR3 就是 CPU 的一個的寄存器,MMU 就是根據(jù)這個寄存器找到頁目錄的。所以,每個進程都有一個頁表基地址,我們每次切換進程都會把當前cpu寄存器的值入棧,這叫環(huán)境保護,等cpu再次切換回來的時候出棧,恢復cpu寄存器大值,這叫環(huán)境恢復。
七、內(nèi)存映射原理分析
內(nèi)存映射即在進程的虛擬地址空間中創(chuàng)建一個映射,分為兩種: (1)文件映射:文件支持的內(nèi)存映射,把文件的一個區(qū)間映射到進程的虛擬地址空間, 數(shù)據(jù)源是存儲設備上的文件。 (2)匿名映射:沒有文件支持的內(nèi)存映射,把物理內(nèi)存映射到進程的虛擬地址空間, 沒有數(shù)據(jù)源。
創(chuàng)建內(nèi)存映射時,在進程的用戶虛擬地址空間中分配一個虛擬內(nèi)存區(qū)域。內(nèi)核采用延遲分配物理內(nèi)存的策略,在進程第一次訪問虛擬頁的時候,產(chǎn)生缺頁異常。如果是文件映射,那么分配物理頁,把文件指定區(qū)間的數(shù)據(jù)讀到物理頁中,然后在頁表中把虛擬頁映射到物理頁。如果是匿名映射,就分配物理頁,然后在頁表中把虛擬頁映射到物理頁。 我們的內(nèi)存空間是分成一段段的,這叫分段機制。分段機制下的虛擬地址由兩部分組成,段選擇子和段內(nèi)偏移量。段選擇子就保存在咱們前面講過的段寄存器里面。段選擇子里面最重要的是段號,用作段表的索引。段表里面保存的是這個段的基地址、段的界限和特權等級等。虛擬地址中的段內(nèi)偏移量應該位于 0 和段界限之間。如果段內(nèi)偏移量是合法的,就將段基地址加上段內(nèi)偏移量得到物理內(nèi)存地址。 其實 Linux 傾向于另外一種從虛擬地址到物理地址的轉換方式,稱為分頁(Paging)。對于物理內(nèi)存,操作系統(tǒng)把它分成一塊一塊大小相同的頁,這樣更方便管理,例如有的內(nèi)存頁面長時間不用了,可以暫時寫到硬盤上,稱為換出。一旦需要的時候,再加載進來,叫做換入。這樣可以擴大可用物理內(nèi)存的大小,提高物理內(nèi)存的利用率。這個換入和換出都是以頁為單位的。頁面的大小一般為 4KB。為了能夠定位和訪問每個頁,需要有個頁表,保存每個頁的起始地址,再加上在頁內(nèi)的偏移量,組成線性地址,就能對于內(nèi)存中的每個位置進行訪問了。

虛擬地址分為兩部分,頁號和頁內(nèi)偏移。頁號作為頁表的索引,頁表包含物理頁每頁所在物理內(nèi)存的基地址。這個基地址與頁內(nèi)偏移的組合就形成了物理內(nèi)存地址。 32 位環(huán)境下,虛擬地址空間共 4GB。如果分成 4KB 一個頁,那就是 1M 個頁。每個頁表項需要 4 個字節(jié)來存儲,那么整個 4GB 空間的映射就需要 4MB 的內(nèi)存來存儲映射表。如果每個進程都有自己的映射表,100 個進程就需要 400MB 的內(nèi)存。對于內(nèi)核來講,有點大了 。 頁表中所有頁表項必須提前建好,并且要求是連續(xù)的。如果不連續(xù),就沒有辦法通過虛擬地址里面的頁號找到對應的頁表項了。 那怎么辦呢?我們可以試著將頁表再分頁,4G 的空間需要 4M 的頁表來存儲映射。我們把這 4M 分成 1K(1024)個 4K,每個 4K 又能放在一頁里面,這樣 1K 個 4K 就是 1K 個頁,這 1K 個頁也需要一個表進行管理,我們稱為頁目錄表,這個頁目錄表里面有 1K 項,每項 4 個字節(jié),頁目錄表大小也是 4K。 頁目錄有 1K 項,用 10 位就可以表示訪問頁目錄的哪一項。這一項其實對應的是一整頁的頁表項,也即 4K 的頁表項。每個頁表項也是 4 個字節(jié),因而一整頁的頁表項是 1K 個。再用 10 位就可以表示訪問頁表項的哪一項,頁表項中的一項對應的就是一個頁,是存放數(shù)據(jù)的頁,這個頁的大小是 4K,用 12 位可以定位這個頁內(nèi)的任何一個位置。 這樣加起來正好 32 位,也就是用前 10 位定位到頁目錄表中的一項。將這一項對應的頁表取出來共 1k 項,再用中間 10 位定位到頁表中的一項,將這一項對應的存放數(shù)據(jù)的頁取出來,再用最后 12 位定位到頁中的具體位置訪問數(shù)據(jù)。如下圖所示:

你可能會問,如果這樣的話,映射 4GB 地址空間就需要 4MB+4KB 的內(nèi)存,這樣不是更大了嗎? 當然如果頁是滿的,當時是更大了,但是,我們往往不會為一個進程分配那么多內(nèi)存。 比如說,上面圖中,我們假設只給這個進程分配了一個數(shù)據(jù)頁。如果只使用頁表,也需要完整的 1M 個頁表項共 4M 的內(nèi)存,但是如果使用了頁目錄,頁目錄需要 1K 個全部分配,占用內(nèi)存 4K,但是里面只有一項使用了。到了頁表項,只需要分配能夠管理那個數(shù)據(jù)頁的頁表項頁就可以了,也就是說,最多 4K,這樣內(nèi)存就節(jié)省多了。 當然對于 64 位的系統(tǒng),兩級肯定不夠了,就變成了四級目錄,分別是全局頁目錄項 PGD(Page Global Directory)、上層頁目錄項 PUD(Page Upper Directory)、中間頁目錄項 PMD(Page Middle Directory)和頁表項 PTE(Page Table Entry)。

