英語

物理記憶體模型

系統中的物理記憶體可以用不同的方式定址。最簡單的情況是,物理記憶體從地址 0 開始,並跨越一個連續的範圍,直到最大地址。然而,這個範圍可能包含一些小的空洞,CPU 無法訪問。然後,可能會有幾個完全不同地址的連續範圍。而且,不要忘記 NUMA,不同的記憶體庫連線到不同的 CPU。

Linux 使用兩種記憶體模型之一抽象了這種多樣性:FLATMEM 和 SPARSEMEM。每個架構定義了它支援的記憶體模型,預設的記憶體模型是什麼,以及是否可以手動覆蓋該預設值。

所有記憶體模型都使用排列在一個或多個數組中的 struct page 來跟蹤物理頁面幀的狀態。

無論選擇哪種記憶體模型,物理頁面幀編號 (PFN) 和相應的 struct page 之間都存在一一對映關係。

每個記憶體模型都定義了 pfn_to_page()page_to_pfn() 輔助函式,允許從 PFN 轉換為 struct page,反之亦然。

FLATMEM

最簡單的記憶體模型是 FLATMEM。此模型適用於具有連續或大部分連續物理記憶體的非 NUMA 系統。

在 FLATMEM 記憶體模型中,有一個全域性 mem_map 陣列,它映射了整個物理記憶體。對於大多數架構,空洞在 mem_map 陣列中都有條目。與空洞對應的 struct page 物件永遠不會完全初始化。

要分配 mem_map 陣列,特定於架構的設定程式碼應呼叫 free_area_init() 函式。然而,在呼叫 memblock_free_all() 將所有記憶體交給頁面分配器之前,對映陣列是不可用的。

架構可以釋放不覆蓋實際物理頁面的 mem_map 陣列的部分。在這種情況下,特定於架構的 pfn_valid() 實現應考慮 mem_map 中的空洞。

使用 FLATMEM,PFN 和 struct page 之間的轉換很簡單:PFN - ARCH_PFN_OFFSETmem_map 陣列的索引。

ARCH_PFN_OFFSET 定義了物理記憶體從與 0 不同的地址開始的系統的第一個頁面幀編號。

SPARSEMEM

SPARSEMEM 是 Linux 中最通用的記憶體模型,也是唯一支援幾個高階功能的記憶體模型,例如物理記憶體的熱插拔和熱移除、非易失性記憶體裝置的備用記憶體對映以及更大系統的記憶體對映的延遲初始化。

SPARSEMEM 模型將物理記憶體表示為節的集合。節由 struct mem_section 表示,其中包含 section_mem_map,從邏輯上講,它是指向 struct pages 陣列的指標。但是,它與其他一些魔法一起儲存,有助於節的管理。節的大小和最大節數使用每個支援 SPARSEMEM 的架構定義的 SECTION_SIZE_BITSMAX_PHYSMEM_BITS 常量指定。雖然 MAX_PHYSMEM_BITS 是架構支援的物理地址的實際寬度,但 SECTION_SIZE_BITS 是一個任意值。

最大節數表示為 NR_MEM_SECTIONS,定義為

NR\_MEM\_SECTIONS = 2 ^ {(MAX\_PHYSMEM\_BITS - SECTION\_SIZE\_BITS)}

mem_section 物件排列在一個名為 mem_sections 的二維陣列中。此陣列的大小和位置取決於 CONFIG_SPARSEMEM_EXTREME 和最大可能的節數

  • CONFIG_SPARSEMEM_EXTREME 被停用時,mem_sections 陣列是靜態的,並且具有 NR_MEM_SECTIONS 行。每行包含一個 mem_section 物件。

  • CONFIG_SPARSEMEM_EXTREME 被啟用時,mem_sections 陣列是動態分配的。每行包含 PAGE_SIZE 大小的 mem_section 物件,並且計算行數以適合所有記憶體節。

架構設定程式碼應呼叫 sparse_init() 來初始化記憶體節和記憶體對映。

使用 SPARSEMEM,有兩種可能的方式將 PFN 轉換為相應的 struct page - “經典 sparse” 和 “sparse vmemmap”。選擇在構建時進行,並且由 CONFIG_SPARSEMEM_VMEMMAP 的值確定。

經典 sparse 將頁面的節號編碼到 page->flags 中,並使用 PFN 的高位來訪問對映該頁面幀的節。在一個節中,PFN 是頁面陣列的索引。

sparse vmemmap 使用虛擬對映的記憶體對映來最佳化 pfn_to_page 和 page_to_pfn 操作。有一個全域性 struct page *vmemmap 指標,它指向 struct page 物件的虛擬連續陣列。PFN 是該陣列的索引,並且 struct pagevmemmap 的偏移量是該頁面的 PFN。

要使用 vmemmap,架構必須保留一系列虛擬地址,這些地址將對映包含記憶體對映的物理頁面,並確保 vmemmap 指向該範圍。此外,架構應實現 vmemmap_populate() 方法,該方法將分配物理記憶體併為虛擬記憶體對映建立頁面表。如果架構對 vmemmap 對映沒有任何特殊要求,它可以使用通用記憶體管理提供的預設 vmemmap_populate_basepages()

虛擬對映的記憶體對映允許將永續性記憶體裝置的 struct page 物件儲存在這些裝置上預先分配的儲存空間中。此儲存空間由 struct vmem_altmap 表示,最終透過一長串函式呼叫傳遞給 vmemmap_populate()。vmemmap_populate() 實現可以將 vmem_altmapvmemmap_alloc_block_buf() 輔助函式一起使用,以在永續性記憶體裝置上分配記憶體對映。

ZONE_DEVICE

ZONE_DEVICE 設施構建在 SPARSEMEM_VMEMMAP 之上,為裝置驅動程式標識的物理地址範圍提供 struct page mem_map 服務。ZONE_DEVICE 的“裝置”方面與這些地址範圍的頁面物件永遠不會標記為 online 的事實有關,並且必須針對裝置進行引用,而不僅僅是頁面,以使記憶體保持固定以供主動使用。ZONE_DEVICE,透過 devm_memremap_pages(),執行足夠的記憶體熱插拔以開啟給定 pfn 範圍的 pfn_to_page()page_to_pfn()get_user_pages() 服務。由於頁面引用計數永遠不會降到 1 以下,因此該頁面永遠不會被跟蹤為可用記憶體,並且該頁面的 struct list_head lru 空間被重新用於反向引用到對映記憶體的主機裝置/驅動程式。

雖然 SPARSEMEM 將記憶體表示為節的集合,可以選擇性地收集到記憶體塊中,但 ZONE_DEVICE 使用者需要更小的粒度來填充 mem_map。鑑於 ZONE_DEVICE 記憶體永遠不會標記為線上,因此它隨後不會受到其記憶體範圍透過 sysfs 記憶體熱插拔 api 在記憶體塊邊界上公開的影響。該實現依賴於缺少使用者 api 約束來允許將子節大小的記憶體範圍指定給 arch_add_memory(),即記憶體熱插拔的上半部分。子節支援允許 2MB 作為 devm_memremap_pages() 的跨架構通用對齊粒度。

ZONE_DEVICE 的使用者是

  • pmem:對映平臺永續性記憶體,以便透過 DAX 對映用作直接 I/O 目標。

  • hmm:使用 ->page_fault()->page_free() 事件回撥擴充套件 ZONE_DEVICE,以允許裝置驅動程式協調與裝置記憶體(通常是 GPU 記憶體)相關的記憶體管理事件。請參閱 異構記憶體管理 (HMM)

  • p2pdma:建立 struct page 物件,以允許 PCI/-E 拓撲中的對等裝置協調它們之間的直接 DMA 操作,即繞過主機記憶體。