2006 年 2 月 23 日 在這篇 Linux? 內存模型指南中,我們將學(xué)習如何構建和管理內存方面的基礎知識。本指南介紹了內存控制單元、分頁(yè)模型方面的內容,并詳細介紹了物理內存區域方面的知識。 理解 Linux 使用的內存模型是從更大程度上掌握 Linux 設計和實(shí)現的第一步,因此本文將概述 Linux 內存模型和管理。 Linux 使用的是單一整體式結構 (Monolithic),其中定義了一組原語(yǔ)或系統調用以實(shí)現操作系統的服務(wù),例如在幾個(gè)模塊中以超級模式運行的進(jìn)程管理、并發(fā)控制和內存管理服務(wù)。盡管出于兼容性考慮,Linux 依然將段控制單元模型 (segment control unit model) 保持一種符號表示,但實(shí)際上已經(jīng)很少使用這種模型了。 與內存管理有關(guān)的主要問(wèn)題有: - 虛擬內存的管理,這是介于應用程序請求與物理內存之間的一個(gè)邏輯層。
- 物理內存的管理。
- 內核虛擬內存的管理/內核內存分配器,這是一個(gè)用來(lái)滿(mǎn)足對內存的請求的組件。這種對內存的請求可能來(lái)自于內核,也可能來(lái)自于用戶(hù)。
- 虛擬地址空間的管理。
- 交換和緩存。
本文探討了以下問(wèn)題,可以幫助您從操作系統中內存管理的角度來(lái)理解 Linux 的內幕: - 段控制單元模型,通常專(zhuān)用于 Linux
- 分頁(yè)模型,通常專(zhuān)用于 Linux
- 物理內存方面的知識
雖然本文并沒(méi)有詳細介紹 Linux 內核管理內存的方法,但是介紹了有關(guān)整個(gè)內存模型的知識以及系統的尋址方式,這些介紹可為您進(jìn)一步的學(xué)習提供一個(gè)框架。本文重點(diǎn)介紹的是 x86 架構,但本文中的知識對于其他硬件實(shí)現同樣適用。 x86 內存架構 在 x86 架構中,內存被劃分成 3 種類(lèi)型的地址: - 邏輯地址 (logical address) 是存儲位置的地址,它可能直接對應于一個(gè)物理位置,也可能不直接對應于一個(gè)物理位置。邏輯地址通常在請求控制器中的信息時(shí)使用。
- 線(xiàn)性地址 (linear address) (或稱(chēng)為 平面地址空間)是從 0 開(kāi)始進(jìn)行尋址的內存。之后的每個(gè)字節都可順序使用下一數字來(lái)引用(0、1、2、3 等),直到內存末尾為止。這就是大部分非 Intel CPU 的尋址方式。Intel? 架構使用了分段的地址空間,其中內存被劃分成 64KB 的段,有一個(gè)段寄存器總是指向當前正在尋址的段的基址。這種架構中的 32 位模式被視為平面地址空間,不過(guò)它也使用了段。
- 物理地址 (physical address) 是使用物理地址總線(xiàn)中的位表示的地址。物理地址可能與邏輯地址不同,內存管理單元可以將邏輯地址轉換成物理地址。
CPU 使用兩種單元將邏輯地址轉換成物理地址。第一種稱(chēng)為分段單元 (segmented unit),另外一種稱(chēng)為分頁(yè)單元 (paging unit)。 圖 1. 轉換地址空間使用的兩種單元 下面讓我們來(lái)介紹一下段控制單元模型。
段控制單元模型概述 這種分段模型背后的基本思想是將內存分段管理。從本質(zhì)上來(lái)說(shuō),每個(gè)段就是自己的地址空間。段由兩個(gè)元素構成: - 基址 (base address) 包含某個(gè)物理內存位置的地址
- 長(cháng)度值 (length value) 指定該段的長(cháng)度
分段地址還包括兩個(gè)組件 —— 段選擇器 (segment selector) 和段內偏移量 (offset into the segment)。段選擇器指定了要使用的段(即基址和長(cháng)度值),而段內偏移量組件則指定了實(shí)際內存位置相對于基址的偏移量。實(shí)際內存位置的物理地址就是這個(gè)基址值與偏移量之和。如果偏移量超過(guò)了段的長(cháng)度,系統就會(huì )生成一個(gè)保護違例錯誤。 上述內容可小結如下: 分段單元可以表示成 -> 段: 偏移量 模型 也也可表示成 -> 段標識符: 偏移量 | 每個(gè)段都是一個(gè) 16 位的字段,稱(chēng)為段標識符 (segment identifier) 或段選擇器 (segment selector)。x86 硬件包括幾個(gè)可編程的寄存器,稱(chēng)為 段寄存器 (segment register),段選擇器保存于其中。這些寄存器為 cs(代碼段)、ds(數據段)和 ss(堆棧段)。每個(gè)段標識符都代表一個(gè)使用 64 位(8 個(gè)字節)的段描述符 (segment descriptor) 表示的段。這些段描述符可以存儲在一個(gè) GDT(全局描述符表,global descriptor table)中,也可以存儲在一個(gè) LDT(本地描述符表,local descriptor table)中。 圖 2. 段描述符和段寄存器的相互關(guān)系 每次將段選擇器加載到段寄存器中時(shí),對應的段描述符都會(huì )從內存加載到相匹配的不可編程 CPU 寄存器中。每個(gè)段描述符長(cháng) 8 個(gè)字節,表示內存中的一個(gè)段。這些都存儲到 LDT 或 GDT 中。段描述符條目中包含一個(gè)指針和一個(gè) 20 位的值(Limit 字段),前者指向由 Base 字段表示的相關(guān)段中的第一個(gè)字節,后者表示內存中段的大小。 其他某些字段還包含一些特殊屬性,例如優(yōu)先級和段的類(lèi)型(cs 或 ds)。段的類(lèi)型是由一個(gè) 4 位的 Type 字段表示的。 由于我們使用了不可編程寄存器,因此在將邏輯地址轉換成線(xiàn)性地址時(shí)不引用 GDT 或 LDT。這樣可以加快內存地址的轉換速度。 段選擇器包含以下內容: - 一個(gè) 13 位的索引,用來(lái)標識 GDT 或 LDT 中包含的對應段描述符條目
- TI (Table Indicator) 標志指定段描述符是在 GDT 中還是在 LDT 中,如果該值是 0,段描述符就在 GDT 中;如果該值是 1,段描述符就在 LDT 中。
- RPL (request privilege level) 定義了在將對應的段選擇器加載到段寄存器中時(shí) CPU 的當前特權級別。
由于一個(gè)段描述符的大小是 8 個(gè)字節,因此它在 GDT 或 LDT 中的相對地址可以這樣計算:段選擇器的高 13 位乘以 8。例如,如果 GDT 存儲在地址 0x00020000 處,而段選擇器的 Index 域是 2,那么對應的段描述符的地址就等于 (2*8) + 0x00020000。GDT 中可以存儲的段描述符的總數等于 (2^13 - 1),即 8191。 圖 3 展示了從邏輯地址獲得線(xiàn)性地址。 圖 3. 從邏輯地址獲得線(xiàn)性地址 那么這在 Linux 環(huán)境下有什么不同呢?
Linux 中的段控制單元 Linux 對這個(gè)模型稍微進(jìn)行了修改。我注意到 Linux 以一種受限的方法來(lái)使用這種分段模型(主要是出于兼容性方面的考慮)。 在 Linux 中,所有的段寄存器都指向相同的段地址范圍 —— 換言之,每個(gè)段寄存器都使用相同的線(xiàn)性地址。這使 Linux 所用的段描述符數量受限,從而可將所有描述符都保存在 GDT 之中。這種模型有兩個(gè)優(yōu)點(diǎn): - 當所有的進(jìn)程都使用相同的段寄存器值時(shí)(當它們共享相同的線(xiàn)性地址空間時(shí)),內存管理更為簡(jiǎn)單。
- 在大部分架構上都可以實(shí)現可移植性。某些 RISC 處理器也可通過(guò)這種受限的方式支持分段。
圖 4 展示了對模型的修改。 圖 4. 在 Linux 中,段寄存器指向相同的地址集 段描述符 Linux 使用以下段描述符: - 內核代碼段
- 內核數據段
- 用戶(hù)代碼段
- 用戶(hù)數據段
- TSS 段
- 默認 LDT 段
下面詳細介紹這些段寄存器。 GDT 中的內核代碼段 (kernel code segment) 描述符中的值如下: - Base = 0x00000000
- Limit = 0xffffffff (2^32 -1) = 4GB
- G(粒度標志)= 1,表示段的大小是以頁(yè)為單位表示的
- S = 1,表示普通代碼或數據段
- Type = 0xa,表示可以讀取或執行的代碼段
- DPL 值 = 0,表示內核模式
與這個(gè)段相關(guān)的線(xiàn)性地址是 4 GB,S = 1 和 type = 0xa 表示代碼段。選擇器在 cs 寄存器中。Linux 中用來(lái)訪(fǎng)問(wèn)這個(gè)段選擇器的宏是 _KERNEL_CS。 內核數據段 (kernel data segment) 描述符的值與內核代碼段的值類(lèi)似,惟一不同的就是 Type 字段值為 2。這表示此段為數據段,選擇器存儲在 ds 寄存器中。Linux 中用來(lái)訪(fǎng)問(wèn)這個(gè)段選擇器的宏是 _KERNEL_DS。 用戶(hù)代碼段 (user code segment) 由處于用戶(hù)模式中的所有進(jìn)程共享。存儲在 GDT 中的對應段描述符的值如下: - Base = 0x00000000
- Limit = 0xffffffff
- G = 1
- S = 1
- Type = 0xa,表示可以讀取和執行的代碼段
- DPL = 3,表示用戶(hù)模式
在 Linux 中,我們可以通過(guò) _USER_CS 宏來(lái)訪(fǎng)問(wèn)此段選擇器。 在 用戶(hù)數據段 (user data segment) 描述符中,惟一不同的字段就是 Type,它被設置為 2,表示將此數據段定義為可讀取和寫(xiě)入。Linux 中用來(lái)訪(fǎng)問(wèn)此段選擇器的宏是 _USER_DS。 除了這些段描述符之外,GDT 還包含了另外兩個(gè)用于每個(gè)創(chuàng )建的進(jìn)程的段描述符 —— TSS 和 LDT 段。 每個(gè) TSS 段 (TSS segment) 描述符都代表一個(gè)不同的進(jìn)程。TSS 中保存了每個(gè) CPU 的硬件上下文信息,它有助于有效地切換上下文。例如,在 U->K 模式的切換中,x86 CPU 就是從 TSS 中獲取內核模式堆棧的地址。 每個(gè)進(jìn)程都有自己在 GDT 中存儲的對應進(jìn)程的 TSS 描述符。這些描述符的值如下: - Base = &tss (對應進(jìn)程描述符的 TSS 字段的地址;例如
&tss_struct)這是在 Linux 內核的 schedule.h 文件中定義的 - Limit = 0xeb (TSS 段的大小是 236 字節)
- Type = 9 或 11
- DPL = 0。用戶(hù)模式不能訪(fǎng)問(wèn) TSS。G 標志被清除
所有進(jìn)程共享默認 LDT 段。默認情況下,其中會(huì )包含一個(gè)空的段描述符。這個(gè)默認 LDT 段描述符存儲在 GDT 中。Linux 所生成的 LDT 的大小是 24 個(gè)字節。默認有 3 個(gè)條目: LDT[0] = 空 LDT[1] = 用戶(hù)代碼段 LDT[2] = 用戶(hù)數據/堆棧段描述符 | 計算任務(wù) 要計算 GDT 中最多可以存儲多少條目,必須先理解 NR_TASKS(這個(gè)變量決定了 Linux 可支持的并發(fā)進(jìn)程數 —— 內核源代碼中的默認值是 512,最多允許有 256 個(gè)到同一實(shí)例的并發(fā)連接)。 GDT 中可存儲的條目總數可通過(guò)以下公式確定: GDT 中的條目數 = 12 + 2 * NR_TASKS。 正如前所述,GDT 可以保存的條目數 = 2^13 -1 = 8192。 | 在這 8192 個(gè)段描述符中,Linux 要使用 6 個(gè)段描述符,另外還有 4 個(gè)描述符將用于 APM 特性(高級電源管理特性),在 GDT 中還有 4 個(gè)條目保留未用。因此,GDT 中的條目數等于 8192 - 14,也就是 8180。 任何情況下,GDT 中的條目數 8180,因此: 2 * NR_TASKS = 8180
NR_TASKS = 8180/2 = 4090 (為什么使用 2 * NR_TASKS?因為對于所創(chuàng )建的每個(gè)進(jìn)程,都不僅要加載一個(gè) TSS 描述符 —— 用來(lái)維護上下文切換的內容,另外還要加載一個(gè) LDT 描述符。) 這種 x86 架構中進(jìn)程數量的限制是 Linux 2.2 中的一個(gè)組件,但自 2.4 版的內核開(kāi)始,這個(gè)問(wèn)題已經(jīng)不存在了,部分原因是使用了硬件上下文切換(這不可避免地要使用 TSS),并將其替換為進(jìn)程切換。 接下來(lái),讓我們了解一下分頁(yè)模型。
分頁(yè)模型概述 分頁(yè)單元負責將線(xiàn)性地址轉換成物理地址(請參見(jiàn)圖 1)。線(xiàn)性地址會(huì )被分組成頁(yè)的形式。這些線(xiàn)性地址實(shí)際上都是連續的 —— 分頁(yè)單元將這些連續的內存映射成對應的連續物理地址范圍(稱(chēng)為 頁(yè)框)。注意,分頁(yè)單元會(huì )直觀(guān)地將 RAM 劃分成固定大小的頁(yè)框。 正因如此,分頁(yè)具有以下優(yōu)點(diǎn): - 為一個(gè)頁(yè)定義的訪(fǎng)問(wèn)權限中保存了構成該頁(yè)的整組線(xiàn)性地址的權限
- 頁(yè)的大小等于頁(yè)框的大小
將這些頁(yè)映射成頁(yè)框的數據結構稱(chēng)為頁(yè)表 (page table)。頁(yè)表存儲在主存儲器中,可由內核在啟用分頁(yè)單元之前對其進(jìn)行恰當的初始化。圖 5 展示了頁(yè)表。 圖 5. 頁(yè)表將頁(yè)轉換成頁(yè)框 注意,上圖 Page1 中包含的地址集正好與 Page Frame1 中包含的地址集匹配。 在 Linux 中,分頁(yè)單元的使用多于分段單元。前面介紹 Linux 分段模型時(shí)已提到,每個(gè)分段描述符都使用相同的地址集進(jìn)行線(xiàn)性尋址,從而盡可能降低使用分段單元將邏輯地址轉換成線(xiàn)性地址的需要。通過(guò)更多地使用分頁(yè)單元而非分段單元,Linux 可以極大地促進(jìn)內存管理及其在不同硬件平臺之間的可移植性。 分頁(yè)過(guò)程中使用的字段 下面讓我們來(lái)介紹一下用于在 x86 架構中指定分頁(yè)的字段,這些字段有助于在 Linux 中實(shí)現分頁(yè)功能。分頁(yè)單元進(jìn)入作為分段單元輸出結果的線(xiàn)性字段,然后進(jìn)一步將其劃分成以下 3 個(gè)字段: - Directory 以 10 MSB 表示(Most Significant Bit,也就是二進(jìn)制數字中值最大的位的位置 —— MSB 有時(shí)稱(chēng)為最左位)。
- Table 以中間的 10 位表示。
- Offset 以 12 LSB 表示。(Least Significant Bit,也就是二進(jìn)制整數中給定單元值的位的位置,即確定這個(gè)數字是奇數還是偶數。LSB 有時(shí)稱(chēng)為最右位。這與數字權重最輕的數字類(lèi)似,它是最右邊位置處的數字。)
線(xiàn)性地址到對應物理位置的轉換的過(guò)程包含兩個(gè)步驟。第一步使用了一個(gè)稱(chēng)為頁(yè)目錄 (Page Directory) 的轉換表(從頁(yè)目錄轉換成頁(yè)表),第二步使用了一個(gè)稱(chēng)為頁(yè)表 (Page Table) 的轉換表(即頁(yè)表加偏移量再加頁(yè)框)。圖 6 展示了此過(guò)程。 圖 6. 分頁(yè)字段 開(kāi)始時(shí),首先將頁(yè)目錄的物理地址加載到 cr3 寄存器中。線(xiàn)性地址中的 Directory 字段確定頁(yè)目錄中指向恰當的頁(yè)表條目。Table 字段中的地址確定包含頁(yè)的頁(yè)框物理地址所在頁(yè)表中的條目。Offset 字段確定了頁(yè)框中的相對位置。由于 Offset 字段為 12 位,因此每個(gè)頁(yè)中都包含有 4 KB 數據。 下面小結物理地址的計算: cr3 + Page Directory (10 MSB) = 指向 table_base table_base + Page Table (10 中間位) = 指向 page_base page_base + Offset = 物理地址 (獲得頁(yè)框) 由于 Page Directory 字段和 Page Table 段都是 10 位,因此其可尋址上限為 1024*1024 KB,Offset 可尋址的范圍最大為 2^12(4096 字節)。因此,頁(yè)目錄的可尋址上限為 1024*1024*4096(等于 2^32 個(gè)內存單元,即 4 GB)。因此在 x86 架構上,總可尋址上限是 4 GB。 擴展分頁(yè) 擴展分頁(yè)是通過(guò)刪除頁(yè)表轉換表實(shí)現的;此后線(xiàn)性地址的劃分即可在頁(yè)目錄 (10 MSB) 和偏移量 (22 LSB) 之間完成了。 22 LSB 構成了頁(yè)框的 4 MB 邊界(2^22)。擴展分頁(yè)可以與普通的分頁(yè)模型一起使用,并可用于將大型的連續線(xiàn)性地址映射為對應的物理地址。操作系統中刪除頁(yè)表以提供擴展頁(yè)表。這可以通過(guò)設置 PSE (page size extension) 實(shí)現。 36 位的 PSE 擴展了 36 位的物理地址,可以支持 4 MB 頁(yè),同時(shí)維護一個(gè) 4 字節的頁(yè)目錄條目,這樣就可以提供一種對超過(guò) 4 GB 的物理內存進(jìn)行尋址的方法,而不需要對操作系統進(jìn)行太大的修改。這種方法對于按需分頁(yè)來(lái)說(shuō)具有一些實(shí)際的限制。
Linux 中的分頁(yè)模型 雖然 Linux 中的分頁(yè)與普通的分頁(yè)類(lèi)似,但是 x86 架構引入了一種三級頁(yè)表機制,包括: - 頁(yè)全局目錄 (Page Global Directory),即 pgd,是多級頁(yè)表的抽象最高層。每一級的頁(yè)表都處理不同大小的內存 —— 這個(gè)全局目錄可以處理 4 MB 的區域。每項都指向一個(gè)更小目錄的低級表,因此 pgd 就是一個(gè)頁(yè)表目錄。當代碼遍歷這個(gè)結構時(shí)(有些驅動(dòng)程序就要這樣做),就稱(chēng)為是在“遍歷”頁(yè)表。
- 頁(yè)中間目錄 (Page Middle Directory),即 pmd,是頁(yè)表的中間層。在 x86 架構上,pmd 在硬件中并不存在,但是在內核代碼中它是與 pgd 合并在一起的。
- 頁(yè)表條目 (Page Table Entry),即 pte,是頁(yè)表的最低層,它直接處理頁(yè)(參看
PAGE_SIZE),該值包含某頁(yè)的物理地址,還包含了說(shuō)明該條目是否有效及相關(guān)頁(yè)是否在物理內存中的位。 為了支持大內存區域,Linux 也采用了這種三級分頁(yè)機制。在不需要為大內存區域時(shí),即可將 pmd 定義成“1”,返回兩級分頁(yè)機制。 分頁(yè)級別是在編譯時(shí)進(jìn)行優(yōu)化的,我們可以通過(guò)啟用或禁用中間目錄來(lái)啟用兩級和三級分頁(yè)(使用相同的代碼)。32 位處理器使用的是 pmd 分頁(yè),而 64 位處理器使用的是 pgd 分頁(yè)。 圖 7. 三級分頁(yè) 如您所知,在 64 位處理器中: - 21 MSB 保留未用
- 13 LSB 由頁(yè)面偏移量表示
- 其余的 30 位分為:
- 10 位用于頁(yè)表
- 10 位用于頁(yè)全局目錄
- 10 位用于頁(yè)中間目錄
我們可以從架構中看到,實(shí)際上使用了 43 位進(jìn)行尋址。因此在 64 位處理器中,可以有效使用的內存是 2 的 43 次方。 每個(gè)進(jìn)程都有自己的頁(yè)目錄和頁(yè)表。為了引用一個(gè)包含實(shí)際用戶(hù)數據的頁(yè)框,操作系統(在 x86 架構上)首先將 pgd 加載到 cr3 寄存器中。Linux 將 cr3 寄存器的內容存儲到 TSS 段中。此后只要在 CPU 上執行新進(jìn)程,就從 TSS 段中將另外一個(gè)值加載到 cr3 寄存器中。從而使分頁(yè)單元引用一組正確的頁(yè)表。 pgd 表中的每一條目都指向一個(gè)頁(yè)框,其中中包含了一組 pmd 條目;pdm 表中的每個(gè)條目又指向一個(gè)頁(yè)框,其中包含一組 pte 條目;pde 表中的每個(gè)條目再指向一個(gè)頁(yè)框,其中包含的是用戶(hù)數據。如果正在查找的頁(yè)已轉出,那么就會(huì )在 pte 表中存儲一個(gè)交換條目,(在缺頁(yè)的情況下)以定位將哪個(gè)頁(yè)框重新加載到內存中。 圖 8 說(shuō)明我們連續為各級頁(yè)表添加偏移量來(lái)映射對應的頁(yè)框條目。我們通過(guò)進(jìn)入作為分段單元輸出的線(xiàn)性地址,再劃分該地址來(lái)獲得偏移量。要將線(xiàn)性地址劃分成對應的每個(gè)頁(yè)表元素,需要在內核中使用不同的宏。本文不詳細介紹這些宏,下面我們通過(guò)圖 8 來(lái)簡(jiǎn)單看一下線(xiàn)性地址的劃分方式。 圖 8. 具有不同地址長(cháng)度的線(xiàn)性地址 預留頁(yè)框 Linux 為內核代碼和數據結構預留了幾個(gè)頁(yè)框。這些頁(yè)永遠不會(huì ) 被轉出到磁盤(pán)上。從 0x0 到 0xc0000000 (PAGE_OFFSET) 的線(xiàn)性地址可由用戶(hù)代碼和內核代碼進(jìn)行引用。從 PAGE_OFFSET 到 0xffffffff 的線(xiàn)性地址只能由內核代碼進(jìn)行訪(fǎng)問(wèn)。 這意味著(zhù)在 4 GB 的內存空間中,只有 3 GB 可以用于用戶(hù)應用程序。 如何啟用分頁(yè) Linux 進(jìn)程使用的分頁(yè)機制包括兩個(gè)階段: - 在啟動(dòng)時(shí),系統為 8 MB 的物理內存設置頁(yè)表。
- 然后,第二個(gè)階段完成對其余物理地址的映射。
在啟動(dòng)階段,startup_32() 調用負責對分頁(yè)機制進(jìn)行初始化。這是在 arch/i386/kernel/head.S 文件中實(shí)現的。這 8 MB 的映射發(fā)生在 PAGE_OFFSET 之上的地址中。這種初始化是通過(guò)一個(gè)靜態(tài)定義的編譯時(shí)數組 (swapper_pg_dir) 開(kāi)始的。在編譯時(shí)它被放到一個(gè)特定的地址(0x00101000)。 這種操作為在代碼中靜態(tài)定義的兩個(gè)頁(yè) —— pg0 和 pg1 —— 建立頁(yè)表。這些頁(yè)框的大小默認為 4 KB,除非我們設置了頁(yè)大小擴展位(有關(guān) PSE 的更多內容,請參閱 擴展分頁(yè) 一節)。這個(gè)全局數組所指向的數據地址存儲在 cr3 寄存器中,我認為這是為 Linux 進(jìn)程設置分頁(yè)單元的第一階段。其余的頁(yè)項是在第二階段中完成的。 第二階段由方法調用 paging_init() 來(lái)完成。 在 32 位的 x86 架構上,RAM 映射到 PAGE_OFFSET 和由 4GB 上限 (0xFFFFFFFF) 表示的地址之間。這意味著(zhù)大約有 1 GB 的 RAM 可以在 Linux 啟動(dòng)時(shí)進(jìn)行映射,這種操作是默認進(jìn)行的。然而,如果有人設置了 HIGHMEM_CONFIG,那么就可以將超過(guò) 1 GB 的內存映射到內核上 —— 切記這是一種臨時(shí)的安排??梢酝ㄟ^(guò)調用 kmap() 實(shí)現。
物理內存區域 我已經(jīng)向您展示了(32 位架構上的) Linux 內核按照 3:1 的比率來(lái)劃分虛擬內存:3 GB 的虛擬內存用于用戶(hù)空間,1 GB 的內存用于內核空間。內核代碼及其數據結構都必須位于這 1 GB 的地址空間中,但是對于此地址空間而言,更大的消費者是物理地址的虛擬映射。 之所以出現這種問(wèn)題,是因為若一段內存沒(méi)有映射到自己的地址空間中,那么內核就不能操作這段內存。因此,內核可以處理的最大內存總量就是可以映射到內核的虛擬地址空間減去需要映射到內核代碼本身上的空間。結果,一個(gè)基于 x86 的 Linux 系統最大可以使用略低于 1 GB 的物理內存。 為了迎合大量用戶(hù)的需要,支持更多內存、提高性能,并建立一種獨立于架構的內存描述方法,Linux 內存模型就必須進(jìn)行改進(jìn)。為了實(shí)現這些目標,新模型將內存劃分成分配給每個(gè) CPU 的空間。每個(gè)空間都稱(chēng)為一個(gè) 節點(diǎn);每個(gè)節點(diǎn)都被劃分成一些 區域。區域(表示內存中的范圍)可以進(jìn)一步劃分為以下類(lèi)型: ZONE_DMA(0-16 MB):包含 ISA/PCI 設備需要的低端物理內存區域中的內存范圍。 ZONE_NORMAL(16-896 MB):由內核直接映射到高端范圍的物理內存的內存范圍。所有的內核操作都只能使用這個(gè)內存區域來(lái)進(jìn)行,因此這是對性能至關(guān)重要的區域。 ZONE_HIGHMEM(896 MB 以及更高的內存):系統中內核不能映像到的其他可用內存。 節點(diǎn)的概念在內核中是使用 struct pglist_data 結構來(lái)實(shí)現的。區域是使用 struct zone_struct 結構來(lái)描述的。物理頁(yè)框是使用 struct Page 結構來(lái)表示的,所有這些 Struct 都保存在全局結構數組 struct mem_map 中,這個(gè)數組存儲在 NORMAL_ZONE 的開(kāi)頭。節點(diǎn)、區域和頁(yè)框之間的基本關(guān)系如圖 9 所示。 圖 9. 節點(diǎn)、區域和頁(yè)框之間的關(guān)系 當實(shí)現了對 Pentium II 的虛擬內存擴展的支持(在 32 位系統上使用 PAE —— Physical Address Extension —— 可以訪(fǎng)問(wèn) 64 GB 的內存)和對 4 GB 的物理內存(同樣是在 32 位系統上)的支持時(shí),高端內存區域就會(huì )出現在內核內存管理中了。這是在 x86 和 SPARC 平臺上引用的一個(gè)概念。通常這 4 GB 的內存可以通過(guò)使用 kmap() 將 ZONE_HIGHMEM 映射到 ZONE_NORMAL 來(lái)進(jìn)行訪(fǎng)問(wèn)。請注意在 32 位的架構上使用超過(guò) 16 GB 的內存是不明智的,即使啟用了 PAE 也是如此。 (PAE 是 Intel 提供的內存地址擴展機制,它通過(guò)在宿主操作系統中使用 Address Windowing Extensions API 為應用程序提供支持,從而讓處理器將可以用來(lái)尋址物理內存的位數從 32 位擴展為 36 位。) 這個(gè)物理內存區域的管理是通過(guò)一個(gè) 區域分配器(zone allocator) 實(shí)現的。它負責將內存劃分為很多區域;它可以將每個(gè)區域作為一個(gè)分配單元使用。每個(gè)特定的分配請求都利用了一組區域,內核可以從這些位置按照從高到低的順序來(lái)進(jìn)行分配。 例如: - 對于某個(gè)用戶(hù)頁(yè)面的請求可以首先從“普通”區域中來(lái)滿(mǎn)足(
ZONE_NORMAL); - 如果失敗,就從
ZONE_HIGHMEM 開(kāi)始嘗試; - 如果這也失敗了,就從
ZONE_DMA 開(kāi)始嘗試。 這種分配的區域列表依次包括 ZONE_NORMAL、ZONE_HIGHMEM 和 ZONE_DMA 區域。另一方面,對于 DMA 頁(yè)的請求可能只能從 DMA 區域中得到滿(mǎn)足,因此這種請求的區域列表就只包含 DMA 區域。
結束語(yǔ) 內存管理是一組非常龐大、復雜且耗時(shí)的任務(wù),也是一個(gè)非常難以實(shí)現的任務(wù),因為我們需要精雕細琢出一個(gè)模型,設計好系統如何在真實(shí)的多程序的環(huán)境中進(jìn)行操作,這是一項非常艱難的工作。諸如調度、分頁(yè)行為和多進(jìn)程的交互組件都向我們提出了相當難度的挑戰。我希望本文可以幫助您了解接受 Linux 內存管理挑戰所需要的一些基本知識,并為您提供一個(gè)起點(diǎn)。
參考資料 學(xué)習 獲得產(chǎn)品和技術(shù) - 定購免費的 SEK for Linux,這有兩張 DVD,包括最新的 IBM for Linux 的試用版軟件,包括 DB2?、Lotus?、Rational?、Tivoli? 和 WebSphere?。
- 在您的下一個(gè)開(kāi)發(fā)項目中采用 IBM 試用版軟件,這可以從 developerWorks 上直接下載。
討論
|