
register_page_bootmem_info是 Linux 內(nèi)核啟動(dòng)階段的一個(gè)初始化函數(shù)用于向 bootmem 分配器注冊每個(gè) NUMA 節(jié)點(diǎn)的元數(shù)據(jù)信息。它位于mm/memory_hotplug.c或arch/x86/mm/init_64.c中是連接 SPARSEMEM 內(nèi)存模型與 bootmem 分配器的關(guān)鍵環(huán)節(jié)。核心作用注冊節(jié)點(diǎn)元數(shù)據(jù)在 SPARSEMEM 模型下每個(gè) NUMA 節(jié)點(diǎn)需要維護(hù)一些啟動(dòng)階段專用的元數(shù)據(jù)例如usemap用于跟蹤 memory section 中哪些頁幀已被使用。pageblock_flags用于記錄 pageblock 的可遷移性類型用于內(nèi)存碎片管理。這些元數(shù)據(jù)在啟動(dòng)早期由 bootmem 分配器分配但它們的struct page描述符需要被注冊到 bootmem 的引用計(jì)數(shù)系統(tǒng)中。register_page_bootmem_info正是完成這項(xiàng)工作的入口。代碼邏輯解析static void __init register_page_bootmem_info(void) { #if defined(CONFIG_NUMA) || defined(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) int i; for_each_online_node(i) register_page_bootmem_info_node(NODE_DATA(i)); #endif }條件編譯只有在以下兩種配置之一啟用時(shí)這個(gè)函數(shù)才有實(shí)際工作CONFIG_NUMANUMA 系統(tǒng)需要為每個(gè)節(jié)點(diǎn)管理獨(dú)立的元數(shù)據(jù)。CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP大頁優(yōu)化 vmemmap 的場景下也需要管理額外的元數(shù)據(jù)。遍歷在線節(jié)點(diǎn)for_each_online_node(i)遍歷系統(tǒng)中所有處于在線狀態(tài)的 NUMA 節(jié)點(diǎn)對每個(gè)節(jié)點(diǎn)調(diào)用register_page_bootmem_info_node(NODE_DATA(i))。register_page_bootmem_info_node的工作這個(gè)輔助函數(shù)會(huì)針對單個(gè)節(jié)點(diǎn)注冊以下元數(shù)據(jù)的struct pagenode-node_page節(jié)點(diǎn)自身的struct page。pgdat-node_zones[].usemap每個(gè) zone 的 usemap 頁面。pgdat-node_zones[].pageblock_flagspageblock 標(biāo)志頁面。對于每一個(gè)這樣的頁面它會(huì)調(diào)用get_page_bootmem()來遞增該頁的引用計(jì)數(shù)通過 bootmem 的引用計(jì)數(shù)機(jī)制確保這些元數(shù)據(jù)在 bootmem 被釋放時(shí)不會(huì)被錯(cuò)誤地回收。設(shè)計(jì)意圖這個(gè)函數(shù)解決了一個(gè)生命周期管理問題SPARSEMEM 的元數(shù)據(jù)在啟動(dòng)早期由 bootmem 分配但它們的生命周期需要與struct page數(shù)組vmemmap保持一致。通過將它們的struct page注冊到 bootmem 的引用計(jì)數(shù)系統(tǒng)中內(nèi)核確保了在 bootmem 釋放時(shí)這些元數(shù)據(jù)不會(huì)被一并釋放從而保證了內(nèi)存管理子系統(tǒng)的穩(wěn)定性。這個(gè)函數(shù)是啟動(dòng)流程中一個(gè)“不起眼但關(guān)鍵”的環(huán)節(jié)——沒有它NUMA 系統(tǒng)或大頁優(yōu)化場景下的內(nèi)存管理元數(shù)據(jù)可能會(huì)在 bootmem 退出時(shí)被錯(cuò)誤回收導(dǎo)致系統(tǒng)崩潰。bootmem 分配器是 Linux 內(nèi)核在啟動(dòng)早期、伙伴系統(tǒng)Buddy System尚未就緒時(shí)使用的臨時(shí)內(nèi)存分配器。它的核心任務(wù)是為內(nèi)核初始化過程提供一套可用的物理內(nèi)存分配機(jī)制直到mem_init()被調(diào)用其管理的內(nèi)存被移交給伙伴系統(tǒng)后它便“功成身退”。核心原理位圖與最先適配bootmem 的實(shí)現(xiàn)相對簡單直接它基于First Fit最先適配算法并使用一個(gè)位圖bitmap來記錄物理頁的分配狀態(tài)。內(nèi)存記錄位圖系統(tǒng)啟動(dòng)時(shí)bootmem 會(huì)為管理的物理內(nèi)存區(qū)域分配一個(gè)位圖。位圖中的每一位對應(yīng)一個(gè)物理頁。1表示該頁已被占用0表示空閑。最先適配算法當(dāng)內(nèi)核申請內(nèi)存時(shí)bootmem 會(huì)從頭掃描位圖找到第一塊足夠大的連續(xù)空閑頁塊進(jìn)行分配。這種算法實(shí)現(xiàn)簡單但效率不高且容易在物理內(nèi)存中留下“空洞”。核心數(shù)據(jù)結(jié)構(gòu)bootmem_databootmem 為每個(gè) NUMA 節(jié)點(diǎn)維護(hù)一個(gè)bootmem_data結(jié)構(gòu)體用于記錄該節(jié)點(diǎn)的內(nèi)存管理信息。字段作用node_boot_start該節(jié)點(diǎn)管理的物理內(nèi)存起始地址node_low_pfn該節(jié)點(diǎn)管理的物理內(nèi)存結(jié)束頁幀號node_bootmem_map指向內(nèi)存記錄位圖的指針last_pos/last_offset記錄上一次分配的位置用于加速后續(xù)的小塊內(nèi)存分配支持小于一頁的分配主要接口與工作流程bootmem 提供了一系列以alloc_bootmem和free_bootmem開頭的接口用于內(nèi)存的分配與釋放。初始化在setup_arch()階段架構(gòu)相關(guān)代碼會(huì)調(diào)用init_bootmem()將可用物理內(nèi)存注冊到位圖中。預(yù)留內(nèi)存通過reserve_bootmem()將內(nèi)核鏡像、initrd、設(shè)備樹等已占用內(nèi)存區(qū)域標(biāo)記為“已使用”。分配與釋放內(nèi)核在初始化期間通過alloc_bootmem()申請內(nèi)存。值得注意的是bootmem 分配的內(nèi)存默認(rèn)會(huì)被清零且如果分配失敗會(huì)觸發(fā) panic這在早期啟動(dòng)階段是合理的策略。交接給伙伴系統(tǒng)當(dāng)內(nèi)核完成基礎(chǔ)初始化調(diào)用mem_init()時(shí)free_all_bootmem()會(huì)遍歷位圖將所有標(biāo)記為“空閑”的頁釋放給伙伴系統(tǒng)。此后bootmem 便不再管理這些內(nèi)存。歷史與現(xiàn)狀被 memblock 取代bootmem 曾是 Linux 內(nèi)核多年的標(biāo)準(zhǔn)引導(dǎo)分配器但隨著系統(tǒng)復(fù)雜度增加其局限性如對非連續(xù)內(nèi)存支持不佳逐漸暴露。memblock作為更現(xiàn)代、更簡潔的引導(dǎo)內(nèi)存管理器被引入并最終取代了 bootmem。從 Linux 5.x 系列開始bootmem 已被完全移除。為了保持兼容性內(nèi)核曾提供mm/nobootmem.c作為包裝層讓舊的 bootmem API 調(diào)用轉(zhuǎn)向 memblock但如今主流架構(gòu)均已直接使用 memblock。