服务器设置和教程 · 27 7 月, 2025

學習教程:Linux中的記憶體管理機制

**引言**
Linux 的記憶體管理機制是作業系統設計中的關鍵部分,確保程式能夠高效、安全地使用記憶體資源。本報告將詳細探討 Linux 記憶體管理的各個方面,包括 MMU 的作用、位址空間的組織、記憶體分配策略(如夥伴系統和 slab 分配器)以及核心記憶體管理的具體實現。

**1. 記憶體管理單元 (MMU) 與位址翻譯**
MMU 是 CPU 的一部分,負責將程式使用的虛擬位址映射到實際的實體位址。這一過程在 x86 體系結構下透過分段和分頁機制實現。

– **分段機制**:CPU 生成邏輯位址,透過分段單元轉換為線性位址。分段允許行程的實體位址空間是非連續的,這為記憶體管理提供了靈活性。一個段由基底位址、界限(長度)和類型定義,類型決定段是否可讀、可寫等屬性。
– **分頁機制**:線性位址進一步透過分頁單元映射到實體位址。分頁機制的關鍵優勢是避免外部碎片(即記憶體區塊之間的空隙),因為它允許記憶體以固定大小的頁(通常 4KB)為單位分配。
– **分頁模型**:在 x86 中,MMU 支援多級分頁:
– 32 位元系統:2 級分頁(頁目錄和頁表),每個頁目錄項指向頁表,每個頁表項指向 4KB 的實體頁。
– 32 位元系統開啟實體位址擴展 (PAE):3 級分頁。
– 64 位元系統:4 級分頁。
– **控制暫存器**:分頁機制由 CR0 暫存器的 PG 位元啟用(PG=1 表示啟用,PG=0 禁用,此時線性位址直接作為實體位址)。CR3 暫存器儲存頁目錄表的實體位址,頁目錄表總是以 4KB 為邊界對齊,因此 CR3 的低 12 位元通常為 0。CR2 儲存最後一次頁錯誤的線性位址,CR4 用於處理虛擬 8086 模式等功能。

**2. Linux 中的分段與分頁**
在 Linux 中,分段機制並未被充分利用。全域描述符表 (GDT) 定義了幾個段(如核心程式碼段、資料段和使用者段),但這些段的基底位址均為 0,界限為 4GB。這意味著 Linux 實際上只使用了一個段,程式中的虛擬位址直接等於線性位址。

– Linux 主要依賴分頁機制來管理記憶體。透過 4 級分頁模型,Linux 相容了 32 位元、64 位元系統及其 PAE 擴展情況。例如,在 32 位元系統中,虛擬位址空間分為使用者態(0-3GB)和核心態(3GB-4GB),而分頁確保了位址映射的靈活性。
– 行程的位址空間由 `mm_struct` 結構描述,每個行程只有一個 `mm_struct`。核心態是共享的,不會觸發缺頁中斷或存取使用者空間,因此核心執行緒的 `task_struct->mm` 為 NULL。

**3. Linux 的記憶體分配與管理**
Linux 為每個行程分配虛擬位址空間,其大小和組織方式取決於體系結構。在 32 位元 x86 系統下,虛擬位址空間為 0-4GB,具體劃分如下:

| 位址範圍 | 用途 |
|———————|—————————————|
| 0-3GB | 使用者態空間,用於使用者程式 |
| 3GB-3GB+896MB | 核心態,直接映射實體記憶體 0-896MB |
| 3GB+896MB-4GB | vmalloc 區域,用於核心映射高階記憶體 |

– **實體記憶體分割槽**:
– **ZONE_DMA(0-16MB)**:專為 DMA(直接記憶體存取)裝置使用,因為 DMA 不經過 MMU,需要連續的實體位址緩衝區。
– **ZONE_NORMAL(16MB-896MB)**:核心可以直接存取的常規記憶體。
– **ZONE_HIGHMEM(896MB 以上)**:高階記憶體,核心無法直接存取,需要透過 vmalloc 或其他映射機制。
– **頁表分配**:
– 核心頁表在系統啟動時透過 `paging_init` 函式完成,將 ZONE_DMA 和 ZONE_NORMAL 的實體頁面與虛擬位址 3GB-3GB+896MB 直接映射。
– 使用者態和核心高階位址透過 MMU 修改虛擬位址與實體位址的映射關係,並清除頁表快取。

**4. 夥伴系統**
夥伴系統是 Linux 用來管理實體記憶體的機制,旨在解決頻繁分配和釋放記憶體導致的外部碎片問題。外部碎片是指記憶體中分散的小塊空閒空間,無法滿足大塊記憶體請求。

– **原理**:夥伴系統將實體記憶體分成不同大小的區塊,滿足以下條件:
– 區塊大小相同。
– 實體位址連續。
– 兩個滿足條件的區塊稱為「夥伴」,可以合併成更大的區塊。
– **實現**:Linux 維護 11 個區塊鏈結串列,每個串列對應 2^0 到 2^11 個頁(4KB 到 4MB)的區塊。例如,最大請求為 1024 頁,對應 4MB 連續 RAM 區塊。每個區塊的起始位址是其大小的整數倍,如 16 頁區塊的起始位址為 16×4KB 的倍數。
– **初始化**:系統啟動時,所有實體記憶體釋放到夥伴系統中,每個記憶體節點維護 per-CPU 快取,用於處理單頁分配。分配和釋放透過夥伴演算法實現,盡量合併小塊為空閒大塊,減少碎片。

**5. slab 分配器**
slab 分配器是 Linux 用來管理小記憶體物件的機制,旨在減少內部碎片(即頁內未充分利用的空間)。它是記憶體預分配的一種方式,犧牲空間換取時間效率,假設分配的記憶體區塊比頁小。

– **設計思想**:將若干頁組合成一個 slab,每個 slab 只儲存同一類資料(如核心物件)。這樣,slab 內部以物件大小為分配粒度,減少頁內碎片。多個儲存同類資料的 slab 組成一個快取(cache,與硬體快取無關)。
– **狀態**:slab 有三種狀態:
– **slabs_full**:完全分配。
– **slabs_free**:完全空閒,可分配。
– **slabs_partial**:部分分配。
– **優點**:
– 減少碎片,尤其適合頻繁分配的小物件(如核心資料結構)。
– 支援物件初始化,避免重複初始化開銷。
– 支援硬體快取對齊和著色,提高快取利用率和效能。
– **缺點**:
– 管理複雜,涉及多個佇列(如處理器本地快取佇列、slab 空閒佇列)。
– 儲存開銷大,每個 slab 需要 `struct slab` 資料結構和 `kmem_bufctl_t` 陣列管理物件。當物件體積小(如 32 位元組)時,陣列可能浪費 1/8 空間。
– 緩衝區回收和效能調校較複雜。

**6. 核心態記憶體管理**
Linux 核心透過 `kmalloc` 和 `vmalloc` 函式管理記憶體分配,基於之前的夥伴系統和 slab 分配器。

– **kmalloc**:
– 用於申請連續實體位址的記憶體,適合小塊記憶體分配。
– 基於 slab 分配器實現,呼叫過程為 `kmalloc -> __kmalloc -> __do_kmalloc`,主要步驟:
1. 透過 `kmalloc_slab` 找到合適的 `kmem_cache` 快取。
2. 透過 `slab_alloc` 向 slab 分配器申請物件記憶體。
– **vmalloc**:
– 用於申請連續虛擬位址空間,但實體記憶體不一定連續。
– 呼叫過程為 `vmalloc -> __vmalloc_node_flags -> __vmalloc_node -> __vmalloc_node_range`,主要步驟:
1. 透過 `__get_vm_area_node` 分配虛擬位址空間。
2. 透過 `alloc_pages` 一頁一頁申請實體記憶體,並為虛擬位址分配頁表映射。

**結論**
Linux 的記憶體管理機制透過 MMU、分頁、夥伴系統和 slab 分配器等元件,實現了高效的記憶體分配和管理。MMU 負責位址翻譯,分頁機制提供了靈活的記憶體映射,夥伴系統解決了外部碎片問題,而 slab 分配器則減少了內部碎片。核心透過 `kmalloc` 和 `vmalloc` 函式管理自己的記憶體空間,確保了系統的穩定性和效能。