英語

物理記憶體

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_DMAZONE_DMA32 歷史上代表了適合無法訪問所有可定址記憶體的外圍裝置進行 DMA 的記憶體。 多年來,有更好更強大的介面來獲取具有 DMA 特定要求的記憶體(使用通用裝置的動態 DMA 對映),但 ZONE_DMAZONE_DMA32 仍然代表對如何訪問它們有限制的記憶體範圍。 根據架構,可以使用 CONFIG_ZONE_DMACONFIG_ZONE_DMA32 配置選項在構建時停用這些區域型別中的一種,甚至兩種。 某些 64 位平臺可能需要這兩個區域,因為它們支援具有不同 DMA 定址限制的外圍裝置。

  • ZONE_NORMAL 用於核心始終可以訪問的普通記憶體。 如果 DMA 裝置支援傳輸到所有可定址記憶體,則可以在此區域中的頁面上執行 DMA 操作。 ZONE_NORMAL 始終啟用。

  • ZONE_HIGHMEM 是核心頁表中未被永久對映覆蓋的物理記憶體部分。 核心只能使用臨時對映訪問此區域中的記憶體。 此區域僅在某些 32 位架構上可用,並透過 CONFIG_HIGHMEM 啟用。

  • ZONE_MOVABLE 用於正常可訪問的記憶體,就像 ZONE_NORMAL 一樣。 區別在於 ZONE_MOVABLE 中大多數頁面的內容是可移動的。 這意味著,雖然這些頁面的虛擬地址不會改變,但它們的內容可能會在不同的物理頁面之間移動。 通常,在記憶體熱插拔期間會填充 ZONE_MOVABLE,但也可以使用 kernelcoremovablecoremovable_node 核心命令列引數在啟動時填充。 有關更多詳細資訊,請參閱 頁面遷移記憶體熱(卸)插拔

  • ZONE_DEVICE 表示駐留在 PMEM 和 GPU 等裝置上的記憶體。 它具有與 RAM 區域型別不同的特性,它的存在是為了為裝置驅動程式識別的物理地址範圍提供 struct page 和記憶體對映服務。ZONE_DEVICE 透過配置選項 CONFIG_ZONE_DEVICE 啟用。

重要的是要注意,許多核心操作只能使用 ZONE_NORMAL 進行,因此它是效能最關鍵的區域。 區域將在第 區域 節中進一步討論。

節點和區域範圍之間的關係由韌體報告的物理記憶體對映、記憶體定址的架構約束和核心命令列中的某些引數決定。

例如,在具有 2 GB RAM 的 x86 UMA 機器上的 32 位核心上,整個記憶體將在節點 0 上,並且將有三個區域:ZONE_DMAZONE_NORMALZONE_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_DMA32ZONE_NORMALZONE_MOVABLE,節點 1 上將有 ZONE_NORMALZONE_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_statesnodemask_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_datainclude/linux/mmzone.h 中宣告。 在這裡,我們簡要介紹一下此結構的欄位

常規

node_zones

此節點的區域。 並非所有區域都可能已填充,但它是完整列表。 它由此節點的 node_zonelists 以及其他節點的 node_zonelists 引用。

node_zonelists

所有節點中所有區域的列表。 此列表定義了分配首選的區域順序。 在核心記憶體管理結構的初始化期間,node_zonelistsmm/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_HOTPLUGCONFIG_DEFERRED_STRUCT_PAGE_INIT 配置選項中的至少一個時才定義。pgdat_resize_lock()pgdat_resize_unlock() 用於在不檢查 CONFIG_MEMORY_HOTPLUGCONFIG_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。

回收控制

另請參閱 頁面回收

kswapd

kswapd 核心執行緒的每個節點例項。

kswapd_waitpfmemalloc_waitreclaim_wait

用於同步記憶體回收工作的工作佇列

nr_writeback_throttled

由於等待髒頁面清理而被限制的任務數。

nr_reclaim_start

在回收受到限制等待寫回時寫入的頁面數。

kswapd_order

控制 kswapd 嘗試回收的順序

kswapd_highest_zoneidx

kswapd 要回收的最高區域索引

kswapd_failures

kswapd 無法回收任何頁面的執行次數

min_unmapped_pages

無法回收的最小未對映檔案支援頁面數。 由 vm.min_unmapped_ratio sysctl 確定。 僅當啟用 CONFIG_NUMA 時才定義。

min_slab_pages

無法回收的最小 SLAB 頁面數。 由 vm.min_slab_ratio sysctl 確定。 僅當啟用 CONFIG_NUMA 時才定義

flags

控制回收行為的標誌。

壓縮控制

kcompactd_max_order

kcompactd 應嘗試實現的頁面順序。

kcompactd_highest_zoneidx

kcompactd 要壓縮的最高區域索引。

kcompactd_wait

用於同步記憶體壓縮任務的工作佇列。

kcompactd

kcompactd 核心執行緒的每個節點例項。

proactive_compact_trigger

確定是否啟用主動壓縮。 由 vm.compaction_proactiveness sysctl 控制。

統計資訊

per_cpu_nodestats

節點的每個 CPU VM 統計資訊

vm_stat

節點的 VM 統計資訊。

區域

正如我們提到的,記憶體中的每個區域都由 struct zone 描述,它是它所屬節點的 node_zones 陣列的一個元素。struct zone 是頁面分配器的核心資料結構。 區域表示物理記憶體範圍,並且可能存在空洞。

頁面分配器使用 GFP 標誌,請參閱 記憶體分配控制,由記憶體分配指定,以確定節點中記憶體分配可以從中分配記憶體的最高區域。 頁面分配器首先從該區域分配記憶體,如果頁面分配器無法從該區域分配請求的記憶體量,它將從節點中的下一個較低區域分配記憶體,該過程將繼續到最低區域(包括最低區域)。 例如,如果一個節點包含 ZONE_DMA32ZONE_NORMALZONE_MOVABLE,並且記憶體分配的最高區域是 ZONE_MOVABLE,則頁面分配器從中分配記憶體的區域順序為 ZONE_MOVABLE > ZONE_NORMAL > ZONE_DMA32

在執行時,區域中的空閒頁面位於每個 CPU 頁面集 (PCP) 或區域的空閒區域中。 每個 CPU 頁面集是核心記憶體管理系統中的一個重要機制。 透過在每個 CPU 上本地處理最頻繁的分配和釋放,每個 CPU 頁面集提高了效能和可擴充套件性,尤其是在具有多個核心的系統上。 核心中的頁面分配器採用兩步記憶體分配策略,首先從每個 CPU 頁面集開始,然後回退到夥伴分配器。 頁面在每個 CPU 頁面集和全域性空閒區域(由夥伴分配器管理)之間批次傳輸。 這最大限度地減少了與全域性夥伴分配器頻繁互動的開銷。

架構特定程式碼呼叫 free_area_init() 來初始化區域。

區域結構

區域結構 struct zoneinclude/linux/mmzone.h 中定義。 在這裡,我們簡要介紹一下此結構的欄位

常規

_watermark

此區域的水印。 當區域中的空閒頁面數低於最小水印時,將忽略提升,分配可能會觸發直接回收和直接壓縮,它也用於限制直接回收。 當區域中的空閒頁面數低於低水印時,kswapd 會被喚醒。 當區域中的空閒頁面數高於高水印時,當未設定 sysctl_numa_balancing_modeNUMA_BALANCING_MEMORY_TIERING 位時,kswapd 會停止回收(區域已平衡)。 提升水印用於記憶體分層和 NUMA 平衡。 當區域中的空閒頁面數高於提升水印時,當設定了 sysctl_numa_balancing_modeNUMA_BALANCING_MEMORY_TIERING 位時,kswapd 會停止回收。 水印由 __setup_per_zone_wmarks() 設定。 最小水印根據 vm.min_free_kbytes sysctl 計算。 其他三個水印根據兩個水印之間的距離設定。 距離本身是考慮到 vm.watermark_scale_factor sysctl 計算的。

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_ratio sysctl 確定核心在防禦這些較低區域方面的積極程度。 如果 vm.lowmem_reserve_ratio sysctl 更改,此陣列會在執行時由 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 頁面集的 batchhigh_minhigh_max 用於計算每個 CPU 頁面集在一次鎖定保持下從夥伴分配器獲取的元素數量以提高效率。 它們還用於確定每個 CPU 頁面集是否在頁面釋放過程中將頁面返回到夥伴分配器。

pageblock_flags

指向zone中頁面塊標誌的指標(標誌列表請參考 include/linux/pageblock-flags.h)。記憶體分配在 setup_usemap() 中。每個頁面塊佔用 NR_PAGEBLOCK_BITS 位。僅當啟用 CONFIG_FLATMEM 時定義。當啟用 CONFIG_SPARSEMEM 時,標誌儲存在 mem_section 中。

zone_start_pfn

zone的起始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_pages

zone跨越的總頁數,包括空洞,計算公式為:spanned_pages = zone_end_pfn - zone_start_pfn。 它由 calculate_node_totalpages() 初始化。

present_pages

zone中存在的物理頁面數量,計算公式為: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_pages

zone中存在的、位於早期啟動時可用的記憶體上的頁面數量,不包括熱插拔的記憶體。僅當啟用 CONFIG_MEMORY_HOTPLUG 時定義,並由 calculate_node_totalpages() 初始化。

cma_pages

為 CMA 使用而保留的頁面。當這些頁面不用於 CMA 時,它們的行為類似於 ZONE_MOVABLE。僅當啟用 CONFIG_CMA 時定義。

name

zone的名稱。它是指向 zone_names 陣列中相應元素的指標。

nr_isolate_pageblock

隔離的頁面塊的數量。 它用於解決由於競爭性地檢索頁面塊的 migratetype 而導致的不正確的空閒頁面計數問題。 受 zone->lock 保護。僅當啟用 CONFIG_MEMORY_ISOLATION 時定義。

span_seqlock

用於保護 zone_start_pfnspanned_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 時定義。

flags

zone 標誌。 使用最少的三個位,並由 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_SKIPPEDCOMPACT_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_stat

zone的 VM 統計資訊。 跟蹤的專案由 enum zone_stat_item 定義。

vm_numa_event

zone的 VM NUMA 事件統計資訊。 跟蹤的專案由 enum numa_stat_item 定義。

per_cpu_zonestats

zone的每個 CPU 的 VM 統計資訊。 它記錄每個 CPU 的 VM 統計資訊和 VM NUMA 事件統計資訊。 它減少了對zone的全域性 vm_statvm_numa_event 欄位的更新,以提高效能。

頁面

存根

本節不完整。請列出並描述相應的欄位。

Folios

存根

本節不完整。請列出並描述相應的欄位。

初始化

存根

本節不完整。請列出並描述相應的欄位。