成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux內核物理內存模型:開啟內存管理之門

系統 Linux
在 Linux 系統的龐大架構里,內存管理無疑是一塊關鍵基石。它肩負著保障系統穩定運行、實現資源高效利用以及提升應用程序性能等多重重任,猶如一位幕后英雄,默默支撐著整個系統的運轉。

在計算機的世界里,內存就像一座神秘的大廈,而 Linux 內核的物理內存模型則是這座大廈的基石。今天,我們將一起走進 Linux 內核物理內存模型的世界,探索它是如何管理和分配內存資源的。這就像是一場穿越時空的旅行,從最基礎的內存架構到復雜的內存管理機制,每一步都將為我們揭開 Linux 內核的神秘面紗。

無論是對計算機技術感興趣的初學者,還是想要深入了解內核的專業人士,都能在這里找到屬于自己的收獲。讓我們開啟這場內存管理的奇妙之旅,去發現 Linux 內核物理內存模型的無限魅力。

一、物理內存模型概述

在 Linux 系統的龐大架構里,內存管理無疑是一塊關鍵基石。它肩負著保障系統穩定運行、實現資源高效利用以及提升應用程序性能等多重重任,猶如一位幕后英雄,默默支撐著整個系統的運轉。當我們同時開啟多個應用程序,比如一邊聽音樂、一邊瀏覽網頁,還后臺運行著文件下載任務,此時內存管理就要像一位精明的管家,合理分配內存資源,讓每個程序都能順暢運行,互不干擾。

這背后靠的就是內存管理對進程內存空間的精細劃分與調度,確保每個進程都有專屬的 “內存領地”,避免數據混亂與沖突。再者,對于資源有限的嵌入式設備,如智能手環、智能家居控制器等,高效的內存管理更是決定設備性能優劣的關鍵。通過優化內存使用,系統能夠快速響應操作指令,避免卡頓,為用戶帶來流暢體驗。毫不夸張地說,深入探究 Linux 內核內存管理機制,是解鎖系統潛能、優化應用性能的必經之路。而物理內存模型作為內存管理的根基,更是值得我們深入挖掘。

操作系統是構建在硬件架構之上的,Linux 自然也不能幸免。目前,主要有兩種類型的物理內存架構:UMA(Uniform Memory Access,一致性內存訪問)架構和 NUMA (Non-Uniform Memory Access,非一致性內存訪問)架構。UMA 將可用內存以連續的方式組織起來,系統中各 CPU 到內存的距離相同,訪問時間一致;NUMA 架構將系統中的內存和 CPU 分成不同的組(節點),每個 CPU 訪問本節點的內存(稱為本地內存,local memory)比訪問其它節點的內存(稱為非本節點內存 non-local memory 或遠端內存 remote memory)速度要快。

在這兩種內存架構的基礎上,分為三種內存模型,分別是:平坦內存模型非連續內存模型稀疏內存模型。平坦內存模型對應著內核配置選項 FLATMEM,非連續內存模型對應著內核配置選項 DISCONTIGMEM,稀疏內存模型對應著內核配置選項 SPARSEMEM 或者 SPARSEMEM_VMEMMAP。

二、體系架構:多樣的內存布局選擇

2.1UMA 架構

在單處理器時期,架構如下圖所示:

圖片圖片

隨著多處理器時代的來臨,架構演變成如下結構:

圖片圖片

在這種架構下,所有的 CPU 位于總線的一側,而所有的內存條組成的整塊內存位于總線的另一側。任何 CPU 想要訪問內存都要經過總線,而且距離都是一樣的,這種架構稱為 SMP(Symmetric Multiprocessing,對稱多處理器)架構。在 SMP 架構下,任何處理器訪問內存的距離是相同的,所以其訪問內存的速度是一致的。這種架構也被成為基于 SMP 的 UMA (Uniform Memory Access,一致性內存訪問)架構。

UMA 架構的特點是簡單,但是有一個顯著的缺點:由于所有處理器訪問內存都要經過總線,當處理器數量很多時,總線就會成為整個系統的瓶頸。

2.2NUMA 架構

在現代多處理器系統的舞臺上,非一致內存訪問(NUMA)架構宛如一顆閃耀的明星,占據著中高端服務器領域的主流地位。它的設計理念猶如一場對傳統內存訪問模式的革新,將物理內存巧妙地劃分成多個獨立的節點,每個節點都緊密簇擁著一組處理器、本地內存以及 I/O 設備,宛如一個個自給自足的 “小王國”。

以一臺配備 NUMA 架構的高性能服務器為例,當處理器在執行任務時,若所需數據存于本地節點的內存中,便能以極快的速度獲取,仿若閃電般迅速;然而,一旦數據位于其他節點的內存,就需通過高速互連通道長途跋涉去調取,這期間的延遲便如同蝸牛爬行,明顯增加。有實驗數據表明,在某些復雜的計算任務中,處理器訪問本地內存的耗時可能僅在幾十納秒,而訪問遠端節點內存的耗時卻會飆升至數百納秒,甚至更多。這種差異在大規模數據處理、高性能計算等對內存訪問速度要求苛刻的場景中,影響可謂深遠。

正因如此,諸多企業級應用,像大型數據庫管理系統、科學計算軟件等,紛紛精心優化內存分配策略,竭力讓數據盡可能靠近使用它的處理器,以充分挖掘 NUMA 架構的性能潛力,確保系統高效運轉。每個節點都有自己的內存(稱為本地內存),并可包含一個或多個處理器。節點和節點之間通過 QPI(Intel QuickPath Interconnect)完成互聯,其架構如下圖所示:

圖片圖片

注:這里的 Core 指的是物理核,HT(Hyper-Threading,超線程)指的是邏輯核。

在 NUMA 架構下,任意一個 CPU 都可以訪問所有節點的內存,訪問自己節點的本地內存是最快的,但訪問其他節點的內存就會慢很多,這就導致了 CPU 訪問內存的速度不一致,所以叫做非一致性內存訪問架構。

NUMA 架構嚴格意義上來講不屬于 SMP 的范疇,但是由于其每個處理器訪問內存的模式是一致的,所以在邏輯上屬于對稱多處理 (SMP) 架構的擴展。

對稱多處理器(SMP)

對稱多處理器(SMP)架構恰似一位秉持公平原則的協調者,在其構建的系統世界里,所有處理器地位平等,共享同一物理內存,無論訪問內存中的哪個地址,所消耗的時間都如同復制粘貼般完全一致。這種一致性使得內存管理在某些場景下顯得簡潔明了,易于掌控。就拿常見的小型服務器或工作站來說,它們處理的任務相對單一,負載較輕,SMP 架構便能輕松應對,充分發揮資源共享的優勢,讓系統流暢運行。

不過,SMP 架構也并非完美無缺,隨著處理器數量的逐步增加,它的短板開始顯現。由于所有處理器都緊緊依賴同一條內存總線去訪問內存,如同眾人爭搶獨木橋,內存訪問沖突愈發激烈,導致內存帶寬迅速成為系統性能提升的瓶頸,限制了系統的進一步擴展。在內核初始化階段,SMP 架構也展現出獨特的一面,0 號處理器勇挑重擔,擔當引導處理器,負責完成內核的初始化工作,而其他處理器則如同乖巧的學生,靜靜等待初始化完成,之后才一同參與系統的運行,攜手并肩處理各項任務。

混合體系結構

在現實復雜多變的應用場景中,一種融合的智慧應運而生 —— 混合體系結構。它巧妙地將 NUMA 與 SMP 的優勢合二為一,恰似一位博采眾長的智者,根據不同的應用需求靈活調配資源,實現性能的優化升華。比如,在大型數據中心的服務器集群里,整體架構采用 NUMA 架構,充分利用其擴展性強、內存訪問局部性好的優勢,應對海量數據的存儲與處理挑戰;

而在每個 NUMA 節點內部,則引入 SMP 架構,讓節點內的多個處理器能夠平等、高效地共享本地內存,協同處理任務,進一步提升執行效率。再如,一些對實時性要求極高的工業控制系統,通過精細的配置,使關鍵任務在 SMP 模式下的處理器上緊密運行,確保響應的及時性;同時,將非關鍵任務合理分配至 NUMA 架構的其他節點,實現資源的優化利用,保障整個系統的穩定與高效。這種混合體系結構,憑借其靈活多變的特性,宛如一把萬能鑰匙,能夠解鎖各種復雜應用場景下的性能密碼,為系統的高效運行保駕護航。

三、內存模型:應對復雜的物理內存

3.1平坦內存(Flat Memory)

在計算機系統的世界里,平坦內存(Flat Memory)模型宛如一位簡潔高效的組織者,適用于那些物理內存連續或近乎連續的非 NUMA 系統場景。想象一下,在一些小型的嵌入式設備或者早期相對簡單的個人計算機系統中,它們的物理內存布局規整,沒有過多復雜的 “縫隙” 與 “空洞”,Flat Memory 模型便能大顯身手。

在這種模型下,內核運用一個全局的 mem_map 數組來精心管理整個物理內存,如同一位嚴謹的圖書管理員,將每一頁物理內存都按照順序在 mem_map 數組中安排得井井有條,數組的下標即為對應的物理頁框號(PFN)。舉例來說,若系統的物理內存從地址 0 開始,依次遞增,毫無間斷,那么 PFN 為 0 的物理頁,恰好對應 mem_map 數組的第 0 個元素,以此類推,這種一一對應的線性關系使得內存管理變得直觀易懂。

不過,現實中的系統架構偶爾會存在一些特殊情況,即便內存整體連續,但某些區域可能因硬件設計、預留等原因無法被正常使用,這些區域就如同圖書館書架上的 “空位”,雖占據位置卻不存放實際書籍。在 mem_map 數組中,它們同樣擁有對應的條目,只不過與之對應的 struct page 對象如同尚未書寫內容的空白紙張,不會被完全初始化,處于一種特殊的待命狀態。

當系統需要分配內存時,只需基于 mem_map 數組進行簡單的偏移計算,就能迅速定位到可用的物理頁,就像讀者在圖書館中根據書架編號快速找到所需書籍一般,高效便捷。

在這種模型下,處理器將物理內存看做是一個連續的,沒有空洞的地址空間。內核定義了一個全局的struct page數組mem_map,用于保存所有的struct page對象。由于struct page對象和 PFN 是一一對應的,所以每個 PFN 對應著mem_map中的一個成員。

⑴全局變量 mem_map

mem_map 是一個全局變量,表示 struct page 數組,其聲明如下:

// file: mm/memory.c
 #ifndef CONFIG_NEED_MULTIPLE_NODES
 ......
 struct page *mem_map;
 ......
 #endif

mem_map 在 alloc_node_mem_map 函數中被初始化。在 NUMA 架構下,每個節點由 struct pglist_data 結構體表示,其中 pglist_data->node_mem_map 字段指示該節點對應的 struct page 數組的起始位置。

// file: include/linux/mmzone.h
 typedef struct pglist_data {
 ......
 #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
     struct page *node_mem_map;
 ......
 #endif
 } pg_data_t;

由于平坦內存模型相當于只有一個節點,所以 mem_map 對應于節點 0 的 pglist_data->node_mem_map 的值。

// file: mm/page_alloc.c
 static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
 {
     /* Skip empty nodes */
     /*
      * pgdat->node_spanned_pages 指示節點中的物理頁數量(包括內存空洞)
      * 如果該值為 0,說明該節點沒有內存,直接跳過
      */
     if (!pgdat->node_spanned_pages)
         return;
 /*
  * 配置選項 CONFIG_FLAT_NODE_MEM_MAP 指示將每個節點的內存當做平坦模型來看待
  * 對應著非稀疏內存模型
  * config FLAT_NODE_MEM_MAP
  *  def_bool y
  *  depends on !SPARSEMEM
  */
 #ifdef CONFIG_FLAT_NODE_MEM_MAP
     /* ia64 gets its own node_mem_map, before this, without bootmem */
     /*
      * pgdat->node_mem_map 指示節點對應的 struct page 數組的起始地址
      * 如果該值為 NULL,說明還未進行初始化,那么就需要對其進行初始化
      */
     if (!pgdat->node_mem_map) {
         unsigned long size, start, end;
         struct page *map;
 
         /*
          * The zone's endpoints aren't required to be MAX_ORDER
          * aligned but the node_mem_map endpoints must be in order
          * for the buddy allocator to function correctly.
          */
         /*
          * MAX_ORDER 指示伙伴系統中的最大分配階,擴展為 11,
          * 表示伙伴系統支持 2 的 0 次方到 2 的 10 次方共 11 種內存分配大小
          * #define MAX_ORDER 11
          * MAX_ORDER_NR_PAGES 指示最大分配階下每次分配的物理頁數量,擴展為 1 << 10 即 1024 個頁,對應 4MB 的內存
          * #define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))
          * 
          * pgdat->node_start_pfn 表示節點的起始頁幀號
          * pgdat_end_pfn(pgdat) 獲取節點的結束頁幀號(包括內存空洞)
          * 二者都需要向上對齊到 MAX_ORDER,這是伙伴系統的要求
          * size 計算出節點對應的 struct page 數組占用的空間
          * 接下來,從節點內存中分配 size 大小的內存用于保存 struct page 數組,內存的起始地址保存到變量 map 中
          * 最后,根據對齊結果,修正 map 的值,并賦值給 pgdat->node_mem_map,用作 struct page 數組的起始地址
          *
          */
         start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
         end = pgdat_end_pfn(pgdat);
         end = ALIGN(end, MAX_ORDER_NR_PAGES);
         size =  (end - start) * sizeof(struct page);
         map = alloc_remap(pgdat->node_id, size);
         if (!map)
             map = alloc_bootmem_node_nopanic(pgdat, size);
         pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
     }
     
 /*
  * 內核配置選項 CONFIG_NEED_MULTIPLE_NODES 表示是否需要多個節點,默認為 yes
  * 配置了該選項意味著是非連續內存模型(DISCONTIGMEM)或者 NUMA 架構
  ************************************************************
  * config NEED_MULTIPLE_NODES
  *  def_bool y
  *  depends on DISCONTIGMEM || NUMA
  ************************************************************
  * 如果沒有設置該選項,說明只有一個節點,對應于平坦內存模型
  */
 #ifndef CONFIG_NEED_MULTIPLE_NODES
     /*
      * With no DISCONTIG, the global mem_map is just set as node 0's
      */
     /*
      * 對于平坦內存模型,將 mem_map 設置為節點 0 的 node_mem_map 的值
      * 宏 NODE_DATA 用于獲取指定節點的 struct pglist_data 實例
      */
     if (pgdat == NODE_DATA(0)) {
         mem_map = NODE_DATA(0)->node_mem_map;
 /*
  * 內核配置選項 CONFIG_HAVE_MEMBLOCK_NODE_MAP 指示對于 memblock 內存塊是否需要區分不同的節點,
  * memblock 用于啟動時內存管理
  * 
  * x86 架構下,該配置項默認為 yes
  * 此時,當 mem_map 對應的頁幀號不等于節點的起始頁幀號時,還需要進一步調整
  */      
 #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
         if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
             mem_map -= (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
 #endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
     }
 #endif
 #endif /* CONFIG_FLAT_NODE_MEM_MAP */
 }

alloc_node_mem_map 函數的完整調用流程如下:

start_kernel() -> setup_arch() -> pagetable_init() -> x86_init.paging.pagetable_init() -> native_pagetable_init() -> paging_init() -> zone_sizes_init() -> free_area_init_nodes() -> free_area_init_node() -> alloc_node_mem_map()。

⑵pfn 和 page 的相互轉換

在平坦內存模型下,pfn 和 struct page 的轉換邏輯相對簡單,如下所示:

// file: include/asm-generic/memory_model.h
 #define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))
 #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + \
                  ARCH_PFN_OFFSET)

可以看出,pfn 和 struct page 實際是線性對應的關系,PFN - ARCH_PFN_OFFSET 就是 mem_map 數組的索引 。其中,mem_map 就是上文提到的全局數組,ARCH_PFN_OFFSET 是與處理器架構相關的頁幀偏移量,其定義了物理內存起始地址不為 0 的系統的第一個頁幀號。對于 x86 架構而言,ARCH_PFN_OFFSET 的值為 0。

為了保證內容的完整性,我們來看下 ARCH_PFN_OFFSET 是如何定義的。

在設置了內核配置選項 CONFIG_FLATMEM 的前提下,如果沒有定義 ARCH_PFN_OFFSET,那么就會將 ARCH_PFN_OFFSET 定義為 0。

// file: include/asm-generic/memory_model.h
 #if defined(CONFIG_FLATMEM)
 
 #ifndef ARCH_PFN_OFFSET
 #define ARCH_PFN_OFFSET     (0UL)
 #endif

實際上,ARCH_PFN_OFFSET 在 include/asm-generic/page.h 文件中是有定義的,但其依賴于 PAGE_OFFSET 和 PAGE_SHIFT 的實現。

// file: include/asm-generic/page.h
 #ifndef ARCH_PFN_OFFSET
 #define ARCH_PFN_OFFSET     (PAGE_OFFSET >> PAGE_SHIFT)
 #endif

正如我們上文所說的,在 4KB 頁的情況下,宏 PAGE_SHIFT 擴展為 12;而宏 PAGE_OFFSET 依賴于內核配置選項 CONFIG_KERNEL_RAM_BASE_ADDRESS。由于 x86 架構不會配置該選項,所以 ARCH_PFN_OFFSET 最終擴展為 0。

// file: include/asm-generic/page.h
 #ifdef CONFIG_KERNEL_RAM_BASE_ADDRESS
 #define PAGE_OFFSET     (CONFIG_KERNEL_RAM_BASE_ADDRESS)
 #else
 #define PAGE_OFFSET     (0)
 #endif

另外,在 x86 架構下,alloc_remap 函數是一個空函數,所以 mem_map 數組內存的分配實際上是在 alloc_bootmem_node_nopanic 函數中進行的。

// file: include/linux/bootmem.h
 static inline void *alloc_remap(int nid, unsigned long size)
 {
     return NULL;
 }

在 alloc_bootmem_node_nopanic 函數的調用鏈中,最終會調用到 __alloc_memory_core_early 函數,該函通過 memblock_find_in_range_node 函數分配到物理內存后,使用宏 phys_to_virt 將物理內存地址轉換成虛擬內存地址。

// file: mm/nobootmem.c
 static void * __init __alloc_memory_core_early(int nid, u64 size, u64 align,
                     u64 goal, u64 limit)
 {
 ......
     addr = memblock_find_in_range_node(goal, limit, size, align, nid);
 ......
     ptr = phys_to_virt(addr);
 ......
     return ptr;
 }

而 phys_to_virt 函數實際等同于 __va 宏。

// file: arch/x86/include/asm/io.h
 static inline void *phys_to_virt(phys_addr_t address)
 {
     return __va(address);
 }

__va(x) 和 __pa(x) 這兩個宏我們在以前的文章中也多次提到過,其功能就是將物理內存地址和直接映射區的虛擬地址進行轉換。換句話說,mem_map 數組位于虛擬地址的直接映射區。平坦內存模型下,struct page與 物理頁對應關系示意圖:

圖片圖片

3.2不連續內存(Discontiguous Memory)

當系統的物理內存空間出現了 “裂縫”,不再是連續的整體,不連續內存(Discontiguous Memory)模型便閃亮登場,成為解決這類復雜內存布局的得力助手。在非一致內存訪問(NUMA)架構的系統中,由于內存被劃分成多個節點,節點之間的內存地址往往存在間斷,就好比一座城市被河流、山脈分隔成多個區域,各個區域內部建筑緊密相連,但區域之間有明顯間隔。此時,Discontiguous Memory 模型便能充分發揮優勢,對這些存在空洞的內存空間進行高效管理。

每個節點都由一個 struct pglist_data 結構體來精心打理,其內部的 node_mem_map 成員如同指向各個區域寶藏的地圖,指向本節點內物理頁描述符數組,確保在這片不連續的內存 “拼圖” 中,每個節點內部的內存管理依然有序。

舉例來說,在一個擁有多個處理器、且內存分布于不同插槽的服務器系統里,每個插槽對應的內存可視為一個獨立節點,處理器訪問本地插槽內存時速度較快,訪問其他插槽內存則相對較慢。Discontiguous Memory 模型通過對不同節點的細致區分,讓系統在面對復雜的內存訪問需求時,能夠精準調度,優先使用本地節點內存,減少跨節點數據傳輸帶來的延遲,如同快遞員優先派送本地包裹,避免長途轉運,提升整體效率。從 PFN 到 struct page 的轉換過程,需先依據 PFN 定位到所屬節點,再通過節點內的 node_mem_map 找到對應的 struct page,這一過程雖然相較平坦內存模型多了一步節點定位,但卻為復雜內存環境下的精準管理提供了可能。

在平坦內存模型下,處理器將物理內存看做一段連續的地址空間。但是,物理內存可能會存在空洞。特別是在 NUMA 架構下,各個節點的物理內存地址不再連續,這樣在節點和節點之間就會出現較大的內存空洞。對于大多數架構,內存空洞在 mem_map 數組中都有對應的 struct page 對象。

也就是說,有些 page 對象實際映射到的是內存空洞。而映射到內存空洞的 struct page 對象永遠不會完全初始化,也無法使用,所以這些 struct page 對象所占用的空間就被白白浪費掉了。

為了解決這個問題,引入了非連續內存模型。在非連續內存模型下,系統將每個節點的內存看做是一段單獨的地址連續的平坦內存,然后在每個節點對應的pglist_data 結構體實例的 node_mem_map 字段中,保存著該節點對應的 struct page 數組的基地址。這樣,就將一段不連續的內存空間分割成了多個連續的內存區間,每段區間都對應著平坦內存模型。

圖片圖片

在非連續內存模型下,PFN 和 struct page 之間的轉換邏輯如下所示:

// file: include/asm-generic/memory_model.h
 #define __pfn_to_page(pfn)          \
 ({  unsigned long __pfn = (pfn);        \
     unsigned long __nid = arch_pfn_to_nid(__pfn);  \
     NODE_DATA(__nid)->node_mem_map + arch_local_page_offset(__pfn, __nid);\
 })
 
 #define __page_to_pfn(pg)                       \
 ({  const struct page *__pg = (pg);                 \
     struct pglist_data *__pgdat = NODE_DATA(page_to_nid(__pg)); \
     (unsigned long)(__pg - __pgdat->node_mem_map) +         \
      __pgdat->node_start_pfn;                   \
 })

每個節點的struct page 數組的起始地址由 pglist_data->node_mem_map 表示(相當于平坦內存模型的 mem_map ),PFN 到節點起始頁幀號( pglist_data->node_start_pfn )的偏移量對應著數組pglist_data->node_mem_map 的索引值,兩者相加就能得到 PFN 對應的 struct page 的地址。

在此之前,先要獲取到 PFN 對應的節點 id(通過 arch_pfn_to_nid),然后通過宏 NODE_DATA 獲取到節點 id 對應的 struct pglist_data 實例及其 node_mem_map 字段的值。宏 arch_local_page_offset 計算指定 PFN 相對于節點起始頁幀的偏移量,即數組的索引。

// file: include/asm-generic/memory_model.h
 #ifndef arch_pfn_to_nid
 #define arch_pfn_to_nid(pfn)    pfn_to_nid(pfn)
 #endif
 
 #ifndef arch_local_page_offset
 #define arch_local_page_offset(pfn, nid)    \
     ((pfn) - NODE_DATA(nid)->node_start_pfn)
 #endif

注意:非連續內存模型已經廢棄不用了。非連續內存模型可以看做稀疏內存模型的一種特例,而且經測試其負載比稀疏內存模型還要高,所以在 x86-64 架構下該內存模型已經被稀疏內存模型所替代。

在 v5.14 之后的內核版本中,CONFIG_DISCONTIGMEM相關的代碼已經被移除了。

3.3稀疏內存(Sparse Memory)

稀疏內存(Sparse Memory)模型作為 Linux 內存管理中的 “全能選手”,專為應對那些物理內存地址空間存在大量空洞,且需要支持內存熱插拔等高級功能的復雜系統而生。在現代大型服務器、云計算平臺等場景中,隨著硬件架構的不斷升級與業務需求的動態變化,系統常常需要在運行過程中靈活增減內存模塊,就如同搭建積木城堡時,隨時可以添加或移除積木塊。Sparse Memory 模型將物理內存巧妙地劃分為多個區段,每個區段用 mem_section 結構體來精細描述,如同將一片廣袤的土地劃分成多個地塊,每個地塊都有詳細的規劃圖。

區段內包含 section_mem_map,從邏輯上講,它如同指向 struct page 陣列的 “指南針”,指引著內核找到對應物理頁的描述信息。在區段劃分方面,不同架構依據自身對物理內存支持的上限以及對內存管理粒度的需求,通過 SECTION_SIZE_BITS 和 MAX_PHYSMEM_BITS 等常量來精心定義區段大小和最大區段數。例如,在某款為大數據處理定制的服務器架構中,區段大小設置為 2^27 字節(即 128MB),如此一來,面對海量數據的存儲與處理,內核能夠以區段為單位靈活調配內存,避免小塊內存頻繁分配回收帶來的性能損耗。對于 PFN 轉換為 struct page,Sparse Memory 模型提供了兩種精妙的方式:“classic sparse” 和 “sparse vmemmap”。

前者如同古老而可靠的地圖,在 page-flags 中巧妙編碼頁面的段號,并利用 PFN 的高位信息精準訪問映射該頁框的段,在區段內,PFN 則作為指向頁數組的索引,指引內核找到目標;后者則像是引入了智能導航系統,借助虛擬映射的內存映射優化 pfn_to_page 和 page_to_pfn 操作,通過一個全局的 struct page *vmemmap 指針,指向虛擬連續的 struct page 對象陣列,此時 PFN 搖身一變成為該數組的索引,struct page 從 vmemmap 的偏移量恰好就是該頁的 PFN,這種方式大大加速了內存管理操作的速度,為系統高效運行提供了有力保障。

隨著內存熱插拔技術的出現,不止節點間內存地址不連續,單個節點內的內存地址不連續也成了常態。這時候,再用非連續內存模型就不合適了,于是又引入了稀疏內存模型 SPARSEMEM。稀疏內存模型是 Linux 中最通用的內存模型,其實不管是平坦內存模型還是非連續內存模型,都可以看做是稀疏內存模型的一種特殊狀態。

稀疏內存模型使用 section 來管理 struct page 數組,section 替代了非連續內存模型中的節點的角色。由于每個 section 管理的 struct page 數量比節點要少的多,所以管理的粒度更細,更適合大塊空洞很多的場景。

section 由 struct mem_section 表示,其中的 section_mem_map 字段在邏輯上是指向 struct page 數組的指針。

// file: include/linux/mmzone.h
 struct mem_section {
     unsigned long section_mem_map;
 ......
 };

類比非連續內存模型,稀疏內存模型示意如下:

圖片圖片

是不是跟非連續內存模型非常相似?當然,上圖只是一個簡單的示意,并沒有展示出 section 的組織方式。另外,在稀疏內存模型中, struct page 在虛擬地址中的布局也分為兩種,上圖也只展示了一種。

四、三級結構:精細的內存組織框架

4.1內存節點(Node)

在 Linux 內核的內存管理體系中,內存節點(Node)宛如一座宏偉建筑的基石,處于最頂層架構,是內存管理的關鍵起點。在非一致內存訪問(NUMA)系統里,內存節點的劃分猶如一場精心策劃的布局,依據處理器與內存的親密程度而定。每個處理器都有自己的 “近鄰” 內存,它們緊密協作,組成一個內存節點,就像一個個以處理器為核心的 “小部落”。

如此一來,當處理器處理任務時,優先訪問本地節點內存,能大幅縮短數據獲取時間,如同在自家倉庫取物般便捷高效,極大提升系統性能。而在具有不連續內存的統一內存訪問(UMA)系統中,內存節點的界定則側重于物理地址的連續性。那些連續的內存區域被劃分成獨立節點,如同將一片廣袤但有間斷的土地,按照連續的地塊劃分成不同區域,各自管理。

內存節點通過一個名為 pglist_data 的結構體來精準描述內存布局,這個結構體如同一位盡職的管家,事無巨細地記錄著內存節點的各項信息。以一個典型的 NUMA 節點為例,node_zones 成員如同一個收納盒,存放著多個內存區域(Zone)結構體,將內存按不同特性細分;node_zonelists 則像是一張備用路線圖,指向備用的內存區域列表,以備不時之需;nr_zones 記錄著該節點內內存區域的數量,如同清點家中房間數量般清晰明了。

再看 node_mem_map 成員,它仿若指向寶藏的指針,指向頁描述符數組,每個物理頁都有對應的頁描述符,就像圖書館里每本書都有專屬的目錄卡片,詳細記錄著書籍信息。不過,它可能并非指向數組的首元素,這背后是為了滿足頁分配器對內存對齊的嚴格要求,確保內存分配的高效與穩定,如同建筑工人按照標準尺寸砌墻,保障墻體堅固整齊。

4.2內存區域(Zone)

深入到內存節點內部,我們會發現它被進一步細分為多個內存區域(Zone),就像一座城市被劃分成不同功能區。內核精心定義了多種區域類型,每種都有其獨特使命。ZONE_DMA,作為直接內存訪問(DMA)區域,宛如一條為特定設備開辟的專屬通道。在一些老舊的工業標準體系結構(ISA)設備中,由于硬件限制,它們只能在特定的低地址內存區域(通常是 0 - 16MB)進行 DMA 操作,這個區域就是它們的 “舞臺”,確保數據能夠順暢傳輸,避免因地址不匹配導致的傳輸故障。

ZONE_DMA32 則像是為 64 位系統中的 “特殊居民” 準備的專屬領地,它適用于那些僅支持 32 位地址尋址進行 DMA 操作的設備,為它們在低 4GB 的內存空間里預留位置,保障設備與內存的協同工作。ZONE_NORMAL 是內存區域中的 “主力軍”,常規可直接映射到內核空間的內存大多在此區域,如同城市的中心商業區,承擔著主要的內存使用任務。

對于 ARM 處理器,雖然內核空間與物理內存存在映射關系,但仍需借助頁表進行精細的地址轉換,如同通過詳細的地圖導航才能找到目的地;而 MIPS 處理器在這方面則相對簡便,部分情況下無需復雜的頁表映射,就能快速定位內存地址。在 32 位時代,ZONE_HIGHMEM 作為高端內存區域,是應對內存尋址局限的無奈之舉。當時內核地址空間僅有 1GB,對于高于 896MB 的物理內存,無法直接映射,只能將其歸入此區域,后續通過特殊映射方式來使用,如同將高處的物品通過特殊工具搬運下來。

到了 64 位系統,內核虛擬地址空間得到極大拓展,如同擁有了一個巨大的倉庫,不再需要 ZONE_HIGHMEM 這個 “臨時儲物間”,所有內存都能得到妥善安置。此外,還有 ZONE_MOVABLE 這個特殊的 “機動部隊”,它像是內存管理中的 “潤滑劑”,通過靈活遷移頁面,有效防止內存碎片的產生,保障內存分配的連續性;ZONE_DEVICE 則專為支持持久內存熱插拔而設立,為設備驅動與內存的交互提供穩定接口,確保設備在熱插拔過程中內存使用的穩定性。

4.3頁(Page)

在 Linux 內核眼中,物理內存是以頁(Page)為基本單位進行精細管理的,頁就如同建筑中的磚塊,是構成內存大廈的基礎單元。每一個物理頁面對應著一個 struct page 結構體,這個結構體如同一位忠誠的衛士,時刻跟蹤著頁面的使用情況。在處理器內部,有一個神奇的 MMU(內存管理單元)硬件,它如同一位翻譯官,負責處理虛擬內存到物理內存的映射,將程序使用的虛擬地址轉換為實際的物理地址,而頁表就是它的翻譯 “詞典”。

在常見的系統中,頁的大小通常為 4KB,這是綜合考慮內存利用效率、硬件尋址能力等多方面因素后的權衡結果。就像將一塊大的內存 “蛋糕” 切成大小合適的 “小塊”,方便分配與管理。以一個運行多個應用程序的系統為例,當程序請求內存時,內核會以頁為單位進行分配,若程序需要 8KB 內存,內核會分配兩個連續的 4KB 頁面,確保內存分配的規整與高效。

同時,頁框(Page Frame)作為物理內存的存儲單元,與頁一一對應,頁框號(PFN)則像每個頁框的 “身份證號”,唯一標識每個頁框。通過 PFN,內核能夠快速定位到對應的物理頁,如同通過身份證號查找人員信息般迅速準確。在內存分配回收、頁面置換等一系列內存管理操作中,struct page 結構體記錄著頁面的狀態信息,如是否空閑、是否被鎖定、屬于哪個進程等,為內核的決策提供關鍵依據,保障系統內存的合理利用與穩定運行。

五、實戰案例:物理內存模型的應用展現

在科學計算領域,物理內存模型的優勢盡顯無遺。就拿大規模的天體物理模擬運算來說,科學家們需要處理海量的數據,這些數據如同浩瀚宇宙中的繁星,數量極其龐大。在采用 NUMA 架構的超級計算機上運行模擬程序時,物理內存模型充分發揮作用。它將內存劃分為多個節點,每個節點緊密關聯著一組處理器。

模擬程序在運行過程中,那些頻繁交互的數據被巧妙地分配到各個處理器對應的本地內存節點中。就像一個分工明確的科研團隊,每個成員專注處理自己手頭與本地資源緊密相關的數據,大幅減少了數據跨節點傳輸的延遲。原本可能需要耗費數小時甚至數天的模擬計算,借助物理內存模型對內存的精細管理,運算時間大幅縮短,讓科學家們能夠更快地探索宇宙的奧秘,推動科學研究的步伐。

再看云計算環境,眾多用戶的虛擬機如同一個個 “虛擬房客”,共享著物理服務器的資源。這里的物理內存模型就像一位智慧的房東,要合理分配內存,滿足不同 “房客” 的需求。當某個時間段內,部分虛擬機運行著對內存需求較大的企業級應用,如大型數據庫管理系統、電商平臺的后臺服務等,物理內存模型會依據系統負載情況,動態調整內存分配。

對于那些處理關鍵業務、實時性要求高的虛擬機,優先保障其內存需求,將內存資源從相對空閑的區域調配過來,確保它們運行流暢,避免因內存不足導致的卡頓或服務中斷。這就如同在繁忙的酒店里,優先滿足重要客人的房間需求,合理安排空房資源,讓整個酒店的運營有條不紊,為云計算用戶提供穩定可靠的服務體驗。

在嵌入式系統領域,以智能汽車的車載控制系統為例,物理內存模型同樣發揮著關鍵作用。智能汽車內部有眾多的電子控制單元(ECU),像發動機控制、自動駕駛輔助、車載娛樂等系統,它們都運行在有限的嵌入式芯片上。這些芯片采用的物理內存模型針對嵌入式系統的特點進行優化,在內存布局上,將與車輛安全、實時操控緊密相關的功能模塊,如剎車控制、轉向助力等,對應的內存區域設置為高優先級,確保數據讀寫的及時性。

同時,對于車載娛樂等非關鍵系統,在內存使用緊張時,適當限制其內存占用,優先保障行車安全相關功能的穩定運行。這種精準的內存管理,就像一位經驗豐富的駕駛員,在復雜路況下合理分配精力,確保車輛安全平穩地行駛在道路上,為駕乘人員保駕護航。

六、回顧總結

回顧 Linux Kernel 物理內存模型,從多樣的體系架構,到應對復雜物理內存的不同模型,再到精細的三級結構,每一處設計都凝聚著開發者的智慧,旨在應對不同硬件平臺與應用場景的挑戰,保障系統高效穩定運行。如今,隨著技術浪潮滾滾向前,內存模型也在持續演進。

硬件層面,新型非易失性內存不斷涌現,如 3D XPoint 技術帶來的傲騰內存,兼具高速讀寫與持久存儲特性,促使內核調整內存管理策略,以充分釋放其潛能;軟件應用領域,容器化技術、實時系統對內存隔離性、確定性提出更高要求,推動內存模型優化改進。展望未來,Linux Kernel 物理內存模型必將在創新與需求的雙重驅動下,持續進化,為系統性能提升、功能拓展筑牢根基。希望本文能成為您探索 Linux 內核的得力助手,激發您深入鉆研內核奧秘的熱情,一同見證 Linux 技術生態的蓬勃發展。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2018-05-18 09:07:43

Linux內核內存

2013-10-11 17:32:18

Linux運維內存管理

2019-12-26 08:45:46

Linux虛擬內存

2018-12-06 10:22:54

Linux內核內存

2025-01-02 11:06:22

2018-03-01 16:25:52

Linux內核內存管理

2025-06-10 01:22:00

2023-10-18 13:31:00

Linux內存

2020-07-07 07:57:39

Linux內存碎片化

2009-10-19 09:45:06

linux內存存管理

2024-03-15 08:54:59

Linux內核NUMA

2013-10-12 13:01:51

Linux運維內存管理

2018-12-06 10:40:50

磁盤緩存內存

2025-03-21 00:00:00

2025-04-07 04:20:00

Linux操作系統內存管理

2017-05-18 16:30:29

Linux內存管理

2022-08-08 08:31:00

Linux內存管理

2017-07-25 15:09:48

Linux地址轉化

2021-11-05 15:00:33

鴻蒙HarmonyOS應用

2021-11-08 15:06:15

鴻蒙HarmonyOS應用
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久av一区| 欧美日产国产成人免费图片 | 婷婷激情综合 | 国产美女一区二区 | 国产精品精品视频一区二区三区 | 久久久激情视频 | 在线不卡av | 欧美日韩在线国产 | 国产三级日本三级 | 激情久久av一区av二区av三区 | 久久精品99| 七七婷婷婷婷精品国产 | 国产视频一区二区在线观看 | 天天干天天爱天天爽 | 99久久99久久精品国产片果冰 | 国产视频不卡一区 | 久久www免费人成看片高清 | 国产成人精品久久二区二区 | 亚洲一区视频在线播放 | 色综合欧美 | 国产一区二区精品在线 | 久99久视频| 欧美成人h版在线观看 | 国产激情在线 | 国产成人精品av | 91久久久久久久久久久 | 欧美成人h版在线观看 | 亚洲一区二区黄 | 免费午夜视频 | 久久久久久久一区 | 亚洲黄色片免费观看 | 天天干,夜夜操 | 国产精品一区久久久 | 久久久久久久久国产 | 久久精品一级 | 午夜精 | 99久久精品国产毛片 | 三级免费毛片 | 日韩欧美在线免费观看 | 久久免费视频2 | 国产精品免费观看 |