物理記憶體¶
Linux 可用於各種架構,因此需要一種架構無關的抽象來表示物理記憶體。 本章介紹用於管理執行系統中的物理記憶體的結構。
記憶體管理中流行的第一個主要概念是 非一致性記憶體訪問 (NUMA)。 對於多核和多插槽機器,記憶體可以排列成儲存體,這些儲存體的訪問成本因與處理器的“距離”而異。 例如,可能有一個分配給每個 CPU 的記憶體庫,或者一個非常適合外圍裝置附近 DMA 的記憶體庫。
每個儲存體稱為一個節點,該概念在 Linux 下由 struct pglist_data 表示,即使該架構是 UMA。 此結構始終由其 typedef pg_data_t 引用。 特定節點的 pg_data_t 結構可以透過 NODE_DATA(nid) 宏引用,其中 nid 是該節點的 ID。
對於 NUMA 架構,節點結構由架構特定程式碼在啟動早期分配。 通常,這些結構在它們代表的記憶體庫上本地分配。 對於 UMA 架構,僅使用一個靜態 pg_data_t 結構,稱為 contig_page_data。 將在第 節點 節中進一步討論節點
整個物理地址空間被劃分為一個或多個塊,稱為區域,這些區域表示記憶體中的範圍。 這些範圍通常由訪問物理記憶體的架構約束決定。 節點內對應於特定區域的記憶體範圍由 struct zone 描述。 每個區域都有以下描述的型別之一。
ZONE_DMA和ZONE_DMA32歷史上代表了適合無法訪問所有可定址記憶體的外圍裝置進行 DMA 的記憶體。 多年來,有更好更強大的介面來獲取具有 DMA 特定要求的記憶體(使用通用裝置的動態 DMA 對映),但ZONE_DMA和ZONE_DMA32仍然代表對如何訪問它們有限制的記憶體範圍。 根據架構,可以使用CONFIG_ZONE_DMA和CONFIG_ZONE_DMA32配置選項在構建時停用這些區域型別中的一種,甚至兩種。 某些 64 位平臺可能需要這兩個區域,因為它們支援具有不同 DMA 定址限制的外圍裝置。ZONE_NORMAL用於核心始終可以訪問的普通記憶體。 如果 DMA 裝置支援傳輸到所有可定址記憶體,則可以在此區域中的頁面上執行 DMA 操作。ZONE_NORMAL始終啟用。ZONE_HIGHMEM是核心頁表中未被永久對映覆蓋的物理記憶體部分。 核心只能使用臨時對映訪問此區域中的記憶體。 此區域僅在某些 32 位架構上可用,並透過CONFIG_HIGHMEM啟用。ZONE_MOVABLE用於正常可訪問的記憶體,就像ZONE_NORMAL一樣。 區別在於ZONE_MOVABLE中大多數頁面的內容是可移動的。 這意味著,雖然這些頁面的虛擬地址不會改變,但它們的內容可能會在不同的物理頁面之間移動。 通常,在記憶體熱插拔期間會填充ZONE_MOVABLE,但也可以使用kernelcore、movablecore和movable_node核心命令列引數在啟動時填充。 有關更多詳細資訊,請參閱 頁面遷移 和 記憶體熱(卸)插拔。ZONE_DEVICE表示駐留在 PMEM 和 GPU 等裝置上的記憶體。 它具有與 RAM 區域型別不同的特性,它的存在是為了為裝置驅動程式識別的物理地址範圍提供 struct page 和記憶體對映服務。ZONE_DEVICE透過配置選項CONFIG_ZONE_DEVICE啟用。
重要的是要注意,許多核心操作只能使用 ZONE_NORMAL 進行,因此它是效能最關鍵的區域。 區域將在第 區域 節中進一步討論。
節點和區域範圍之間的關係由韌體報告的物理記憶體對映、記憶體定址的架構約束和核心命令列中的某些引數決定。
例如,在具有 2 GB RAM 的 x86 UMA 機器上的 32 位核心上,整個記憶體將在節點 0 上,並且將有三個區域:ZONE_DMA、ZONE_NORMAL 和 ZONE_HIGHMEM
0 2G
+-------------------------------------------------------------+
| node 0 |
+-------------------------------------------------------------+
0 16M 896M 2G
+----------+-----------------------+--------------------------+
| ZONE_DMA | ZONE_NORMAL | ZONE_HIGHMEM |
+----------+-----------------------+--------------------------+
使用停用了 ZONE_DMA 並且啟用了 ZONE_DMA32 且使用 arm64 機器上的 movablecore=80% 引數啟動的核心,其中 16 GB 的 RAM 在兩個節點之間平均分配,節點 0 上將有 ZONE_DMA32、ZONE_NORMAL 和 ZONE_MOVABLE,節點 1 上將有 ZONE_NORMAL 和 ZONE_MOVABLE
1G 9G 17G
+--------------------------------+ +--------------------------+
| node 0 | | node 1 |
+--------------------------------+ +--------------------------+
1G 4G 4200M 9G 9320M 17G
+---------+----------+-----------+ +------------+-------------+
| DMA32 | NORMAL | MOVABLE | | NORMAL | MOVABLE |
+---------+----------+-----------+ +------------+-------------+
記憶體庫可能屬於交錯節點。 在下面的示例中,x86 機器在 4 個記憶體庫中有 16 GB 的 RAM,偶數儲存體屬於節點 0,奇數儲存體屬於節點 1
0 4G 8G 12G 16G
+-------------+ +-------------+ +-------------+ +-------------+
| node 0 | | node 1 | | node 0 | | node 1 |
+-------------+ +-------------+ +-------------+ +-------------+
0 16M 4G
+-----+-------+ +-------------+ +-------------+ +-------------+
| DMA | DMA32 | | NORMAL | | NORMAL | | NORMAL |
+-----+-------+ +-------------+ +-------------+ +-------------+
在這種情況下,節點 0 將跨越 0 到 12 GB,節點 1 將跨越 4 到 16 GB。
節點¶
正如我們提到的,記憶體中的每個節點都由 pg_data_t 描述,它是 struct pglist_data 的 typedef。 分配頁面時,預設情況下 Linux 使用節點本地分配策略從最接近執行 CPU 的節點分配記憶體。 由於程序傾向於在同一 CPU 上執行,因此很可能會使用來自當前節點的記憶體。 使用者可以控制分配策略,如 NUMA 記憶體策略 中所述。
大多數 NUMA 架構都維護一個指向節點結構的指標陣列。 實際結構在啟動早期分配,當時架構特定程式碼解析韌體報告的物理記憶體對映。 節點初始化的大部分發生在啟動過程稍後的 free_area_init() 函式中,該函式將在第 初始化 節中進行描述。
除了節點結構之外,核心還維護一個名為 node_states 的 nodemask_t 位掩碼陣列。 此陣列中的每個位掩碼錶示一組具有特定屬性的節點,如 enum node_states 定義的那樣
N_POSSIBLE該節點可能在某個時候變為線上。
N_ONLINE該節點已線上。
N_NORMAL_MEMORY該節點具有常規記憶體。
N_HIGH_MEMORY該節點具有常規記憶體或高位記憶體。 當
CONFIG_HIGHMEM被停用時,別名為N_NORMAL_MEMORY。N_MEMORY該節點具有記憶體(常規、高位、可移動)
N_CPU該節點具有一個或多個 CPU
對於具有上述屬性的每個節點,將在 node_states[<屬性>] 位掩碼中設定對應於節點 ID 的位。
例如,對於具有常規記憶體和 CPU 的節點 2,將在以下位置設定位 2
node_states[N_POSSIBLE]
node_states[N_ONLINE]
node_states[N_NORMAL_MEMORY]
node_states[N_HIGH_MEMORY]
node_states[N_MEMORY]
node_states[N_CPU]
有關 nodemask 可能的各種操作,請參閱 include/linux/nodemask.h。
除其他事項外,nodemask 用於為節點遍歷提供宏,即 for_each_node() 和 for_each_online_node()。
例如,要為每個線上節點呼叫函式 foo()
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
foo(pgdat);
}
節點結構¶
節點結構 struct pglist_data 在 include/linux/mmzone.h 中宣告。 在這裡,我們簡要介紹一下此結構的欄位
常規¶
node_zones此節點的區域。 並非所有區域都可能已填充,但它是完整列表。 它由此節點的 node_zonelists 以及其他節點的 node_zonelists 引用。
node_zonelists所有節點中所有區域的列表。 此列表定義了分配首選的區域順序。 在核心記憶體管理結構的初始化期間,
node_zonelists由mm/page_alloc.c中的build_zonelists()設定。nr_zones此節點中已填充區域的數量。
node_mem_map對於使用 FLATMEM 記憶體模型的 UMA 系統,節點 0 的
node_mem_map是表示每個物理幀的 struct page 陣列。node_page_ext對於使用 FLATMEM 記憶體模型的 UMA 系統,節點 0 的
node_page_ext是 struct page 擴充套件的陣列。 僅在啟用CONFIG_PAGE_EXTENSION構建的核心中可用。node_start_pfn此節點中起始頁面幀的頁面幀編號。
node_present_pages此節點中存在的物理頁面的總數。
node_spanned_pages物理頁面範圍的總大小,包括空洞。
node_size_lock保護定義節點範圍的欄位的鎖。 僅當啟用
CONFIG_MEMORY_HOTPLUG或CONFIG_DEFERRED_STRUCT_PAGE_INIT配置選項中的至少一個時才定義。pgdat_resize_lock()和pgdat_resize_unlock()用於在不檢查CONFIG_MEMORY_HOTPLUG或CONFIG_DEFERRED_STRUCT_PAGE_INIT的情況下操作node_size_lock。node_id節點的節點 ID (NID),從 0 開始。
totalreserve_pages這是每個節點保留的頁面,使用者空間分配不可用。
first_deferred_pfn如果大型機器上的記憶體初始化被延遲,那麼這是需要初始化的第一個 PFN。 僅當啟用
CONFIG_DEFERRED_STRUCT_PAGE_INIT時才定義deferred_split_queue每個節點都有一個巨型頁面佇列,它們的拆分被延遲。 僅當啟用
CONFIG_TRANSPARENT_HUGEPAGE時才定義。__lruvec每個節點都有一個 lruvec,其中包含 LRU 列表和相關引數。 僅當停用記憶體 cgroup 時才使用。 不應直接訪問它,而應使用
mem_cgroup_lruvec()查詢 lruvec。
回收控制¶
另請參閱 頁面回收。
kswapdkswapd 核心執行緒的每個節點例項。
kswapd_wait、pfmemalloc_wait、reclaim_wait用於同步記憶體回收工作的工作佇列
nr_writeback_throttled由於等待髒頁面清理而被限制的任務數。
nr_reclaim_start在回收受到限制等待寫回時寫入的頁面數。
kswapd_order控制 kswapd 嘗試回收的順序
kswapd_highest_zoneidxkswapd 要回收的最高區域索引
kswapd_failureskswapd 無法回收任何頁面的執行次數
min_unmapped_pages無法回收的最小未對映檔案支援頁面數。 由
vm.min_unmapped_ratiosysctl 確定。 僅當啟用CONFIG_NUMA時才定義。min_slab_pages無法回收的最小 SLAB 頁面數。 由
vm.min_slab_ratio sysctl確定。 僅當啟用CONFIG_NUMA時才定義flags控制回收行為的標誌。
壓縮控制¶
kcompactd_max_orderkcompactd 應嘗試實現的頁面順序。
kcompactd_highest_zoneidxkcompactd 要壓縮的最高區域索引。
kcompactd_wait用於同步記憶體壓縮任務的工作佇列。
kcompactdkcompactd 核心執行緒的每個節點例項。
proactive_compact_trigger確定是否啟用主動壓縮。 由
vm.compaction_proactivenesssysctl 控制。
統計資訊¶
per_cpu_nodestats節點的每個 CPU VM 統計資訊
vm_stat節點的 VM 統計資訊。
區域¶
正如我們提到的,記憶體中的每個區域都由 struct zone 描述,它是它所屬節點的 node_zones 陣列的一個元素。struct zone 是頁面分配器的核心資料結構。 區域表示物理記憶體範圍,並且可能存在空洞。
頁面分配器使用 GFP 標誌,請參閱 記憶體分配控制,由記憶體分配指定,以確定節點中記憶體分配可以從中分配記憶體的最高區域。 頁面分配器首先從該區域分配記憶體,如果頁面分配器無法從該區域分配請求的記憶體量,它將從節點中的下一個較低區域分配記憶體,該過程將繼續到最低區域(包括最低區域)。 例如,如果一個節點包含 ZONE_DMA32、ZONE_NORMAL 和 ZONE_MOVABLE,並且記憶體分配的最高區域是 ZONE_MOVABLE,則頁面分配器從中分配記憶體的區域順序為 ZONE_MOVABLE > ZONE_NORMAL > ZONE_DMA32。
在執行時,區域中的空閒頁面位於每個 CPU 頁面集 (PCP) 或區域的空閒區域中。 每個 CPU 頁面集是核心記憶體管理系統中的一個重要機制。 透過在每個 CPU 上本地處理最頻繁的分配和釋放,每個 CPU 頁面集提高了效能和可擴充套件性,尤其是在具有多個核心的系統上。 核心中的頁面分配器採用兩步記憶體分配策略,首先從每個 CPU 頁面集開始,然後回退到夥伴分配器。 頁面在每個 CPU 頁面集和全域性空閒區域(由夥伴分配器管理)之間批次傳輸。 這最大限度地減少了與全域性夥伴分配器頻繁互動的開銷。
架構特定程式碼呼叫 free_area_init() 來初始化區域。
區域結構¶
區域結構 struct zone 在 include/linux/mmzone.h 中定義。 在這裡,我們簡要介紹一下此結構的欄位
常規¶
_watermark此區域的水印。 當區域中的空閒頁面數低於最小水印時,將忽略提升,分配可能會觸發直接回收和直接壓縮,它也用於限制直接回收。 當區域中的空閒頁面數低於低水印時,kswapd 會被喚醒。 當區域中的空閒頁面數高於高水印時,當未設定
sysctl_numa_balancing_mode的NUMA_BALANCING_MEMORY_TIERING位時,kswapd 會停止回收(區域已平衡)。 提升水印用於記憶體分層和 NUMA 平衡。 當區域中的空閒頁面數高於提升水印時,當設定了sysctl_numa_balancing_mode的NUMA_BALANCING_MEMORY_TIERING位時,kswapd 會停止回收。 水印由__setup_per_zone_wmarks()設定。 最小水印根據vm.min_free_kbytessysctl 計算。 其他三個水印根據兩個水印之間的距離設定。 距離本身是考慮到vm.watermark_scale_factorsysctl 計算的。watermark_boost用於提升水印以增加回收壓力以減少未來回退的可能性並立即喚醒 kswapd 的頁面數,因為節點可能總體上已平衡,並且 kswapd 不會自然喚醒。
nr_reserved_highatomic為高階原子分配保留的頁面數。
nr_free_highatomic保留的 highatomic 頁面塊中的空閒頁面數
lowmem_reserve為記憶體分配在此區域中保留的記憶體量陣列。 例如,如果記憶體分配可以從中分配記憶體的最高區域是
ZONE_MOVABLE,則當嘗試從此區域分配記憶體時,為此分配保留在此區域中的記憶體量為lowmem_reserve[ZONE_MOVABLE]。 這是頁面分配器用於防止可以使用highmem的分配使用太多lowmem的一種機制。 對於highmem機器上的一些專門工作負載,核心允許從lowmem區域分配程序記憶體是危險的。 這是因為該記憶體隨後可以透過mlock()系統呼叫或透過交換空間不可用來固定。vm.lowmem_reserve_ratiosysctl 確定核心在防禦這些較低區域方面的積極程度。 如果vm.lowmem_reserve_ratiosysctl 更改,此陣列會在執行時由setup_per_zone_lowmem_reserve()重新計算。node此區域所屬節點的索引。 僅當啟用
CONFIG_NUMA時才可用,因為 UMA 系統中只有一個區域。zone_pgdat指向此區域所屬節點的
struct pglist_data的指標。per_cpu_pageset指向由
setup_zone_pageset()分配和初始化的每個 CPU 頁面集 (PCP) 的指標。 透過在每個 CPU 上本地處理最頻繁的分配和釋放,PCP 提高了具有多個核心的系統的效能和可擴充套件性。pageset_high_min複製到每個 CPU 頁面集的
high_min以便更快地訪問。pageset_high_max複製到每個 CPU 頁面集的
high_max以便更快地訪問。pageset_batch複製到每個 CPU 頁面集的
batch以便更快地訪問。 每個 CPU 頁面集的batch、high_min和high_max用於計算每個 CPU 頁面集在一次鎖定保持下從夥伴分配器獲取的元素數量以提高效率。 它們還用於確定每個 CPU 頁面集是否在頁面釋放過程中將頁面返回到夥伴分配器。pageblock_flags指向zone中頁面塊標誌的指標(標誌列表請參考
include/linux/pageblock-flags.h)。記憶體分配在setup_usemap()中。每個頁面塊佔用NR_PAGEBLOCK_BITS位。僅當啟用CONFIG_FLATMEM時定義。當啟用CONFIG_SPARSEMEM時,標誌儲存在mem_section中。zone_start_pfnzone的起始pfn。它由
calculate_node_totalpages()初始化。managed_pages夥伴系統管理的實際頁面數量,計算公式為:
managed_pages=present_pages-reserved_pages,其中reserved_pages包括由 memblock 分配器分配的頁面。頁面分配器和 vm 掃描器應使用它來計算各種水位線和閾值。使用atomic_long_xxx()函式訪問它。它在free_area_init_core()中初始化,然後在 memblock 分配器將頁面釋放到夥伴系統時重新初始化。spanned_pageszone跨越的總頁數,包括空洞,計算公式為:
spanned_pages=zone_end_pfn-zone_start_pfn。 它由calculate_node_totalpages()初始化。present_pageszone中存在的物理頁面數量,計算公式為:
present_pages=spanned_pages-absent_pages(空洞中的頁面)。記憶體熱插拔或記憶體電源管理邏輯可以使用它透過檢查(present_pages-managed_pages)來找出未管理的頁面。執行時對present_pages的寫入訪問應受到mem_hotplug_begin/done()的保護。任何不能容忍present_pages漂移的讀取器都應使用get_online_mems()來獲取穩定值。它由calculate_node_totalpages()初始化。present_early_pageszone中存在的、位於早期啟動時可用的記憶體上的頁面數量,不包括熱插拔的記憶體。僅當啟用
CONFIG_MEMORY_HOTPLUG時定義,並由calculate_node_totalpages()初始化。cma_pages為 CMA 使用而保留的頁面。當這些頁面不用於 CMA 時,它們的行為類似於
ZONE_MOVABLE。僅當啟用CONFIG_CMA時定義。namezone的名稱。它是指向
zone_names陣列中相應元素的指標。nr_isolate_pageblock隔離的頁面塊的數量。 它用於解決由於競爭性地檢索頁面塊的 migratetype 而導致的不正確的空閒頁面計數問題。 受
zone->lock保護。僅當啟用CONFIG_MEMORY_ISOLATION時定義。span_seqlock用於保護
zone_start_pfn和spanned_pages的 seqlock。 它是一個 seqlock,因為它必須在zone->lock之外讀取,並且在主分配器路徑中完成。 但是,seqlock 的寫入頻率很低。 僅當啟用CONFIG_MEMORY_HOTPLUG時定義。initialized指示 zone 是否已初始化的標誌。 由啟動期間的
init_currently_empty_zone()設定。free_area空閒區域的陣列,其中每個元素對應於特定的 order,即 2 的冪。夥伴分配器使用此結構來有效地管理空閒記憶體。 分配時,它嘗試找到最小的足夠塊,如果最小的足夠塊大於請求的大小,它將遞迴地拆分為下一個較小的塊,直到達到所需的大小。 釋放頁面後,它可以與其夥伴合併以形成更大的塊。 它由
zone_init_free_lists()初始化。unaccepted_pages要接受的頁面列表。列表上的所有頁面都是
MAX_PAGE_ORDER。僅當啟用CONFIG_UNACCEPTED_MEMORY時定義。flagszone 標誌。 使用最少的三個位,並由
enum zone_flags定義。ZONE_BOOSTED_WATERMARK(bit 0):zone 最近提高了水位線。 當喚醒 kswapd 時清除。ZONE_RECLAIM_ACTIVE(bit 1):kswapd 可能正在掃描該zone。ZONE_BELOW_HIGH(bit 2):zone 低於高水位線。lock主鎖,用於保護特定於zone的頁面分配器的內部資料結構,尤其是保護
free_area。percpu_drift_mark當空閒頁低於此點時,在讀取空閒頁數時會採取其他步驟,以避免每個CPU計數器漂移,從而允許突破水位線。 它在
refresh_zone_stat_thresholds()中更新。
壓縮控制¶
compact_cached_free_pfn下次掃描時壓縮空閒掃描器應從哪裡開始的PFN。
compact_cached_migrate_pfn下次掃描時壓縮遷移掃描器應從哪裡開始的PFN。 此陣列有兩個元素:第一個元素用於
MIGRATE_ASYNC模式,另一個元素用於MIGRATE_SYNC模式。compact_init_migrate_pfn初始遷移 PFN,在啟動時初始化為 0,並在完整壓縮完成後初始化為 zone 中具有可遷移頁面的第一個頁面塊。 它用於檢查掃描是否為整個zone掃描。
compact_init_free_pfn初始空閒 PFN,在啟動時初始化為 0,並初始化為zone中具有空閒
MIGRATE_MOVABLE頁面的最後一個頁面塊。 它用於檢查它是否是掃描的開始。compact_considered自上次失敗以來嘗試的壓縮次數。 當壓縮未能導致頁面分配成功時,會在
defer_compaction()中重置。 當應跳過壓縮時,會在compaction_deferred()中增加 1。在呼叫compact_zone()之前呼叫compaction_deferred(),當compact_zone()返回COMPACT_SUCCESS時呼叫compaction_defer_reset(),當compact_zone()返回COMPACT_PARTIAL_SKIPPED或COMPACT_COMPLETE時呼叫defer_compaction()。compact_defer_shift在再次嘗試之前跳過的壓縮次數是
1<<compact_defer_shift。 在defer_compaction()中增加 1。 當直接壓縮導致頁面分配成功時,在compaction_defer_reset()中重置。 它的最大值是COMPACT_MAX_DEFER_SHIFT。compact_order_failed最小的壓縮失敗 order。 當壓縮成功時,在
compaction_defer_reset()中設定,當壓縮未能導致頁面分配成功時,在defer_compaction()中設定。compact_blockskip_flush當壓縮遷移掃描器和空閒掃描器相遇時設定為 true,這意味著應清除
PB_migrate_skip位。contiguous當zone是連續的時設定為 true(換句話說,沒有空洞)。
統計資訊¶
vm_statzone的 VM 統計資訊。 跟蹤的專案由
enum zone_stat_item定義。vm_numa_eventzone的 VM NUMA 事件統計資訊。 跟蹤的專案由
enum numa_stat_item定義。per_cpu_zonestatszone的每個 CPU 的 VM 統計資訊。 它記錄每個 CPU 的 VM 統計資訊和 VM NUMA 事件統計資訊。 它減少了對zone的全域性
vm_stat和vm_numa_event欄位的更新,以提高效能。
頁面¶
存根
本節不完整。請列出並描述相應的欄位。
Folios¶
存根
本節不完整。請列出並描述相應的欄位。
初始化¶
存根
本節不完整。請列出並描述相應的欄位。