Linux内核分析之内存管理-02

韩乔落

第2章:物理内存模型

基于 Linux 6.12.38 源码


2.1 内存节点 (Node)

2.1.1 UMA vs NUMA 架构

Linux 内核支持两种主要的物理内存架构模型:

UMA (Uniform Memory Access) - 均匀内存访问

定义:所有处理器访问所有内存单元的时间相同,内存是统一共享的。

特点

  • 单一共享内存空间,所有 CPU 通过相同的内存总线访问内存
  • 统一的内存控制器,无本地/远程内存之分
  • 内存访问延迟对所有 CPU 一致
  • 结构简单,编程模型直观

典型应用

  • 单处理器系统
  • 对称多处理器 (SMP) 工作站
  • 小型服务器
  • 嵌入式系统

优缺点

  • ✅ 简单的内存访问模型,易于编程
  • ✅ 无需考虑内存局部性优化
  • ❌ 扩展性差,内存总线成为瓶颈
  • ❌ 多 CPU 竞争内存带宽

UMA 架构示意图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────┐
│ UMA 架构 (SMP 系统) │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU0 CPU1 CPU2 CPU3 │
│ │ │ │ │ │
│ └───────┴───────┴───────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 统一内存控制器 │ │
│ └─────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ │ │
│ │ 物理内存 (统一地址空间) │ │
│ │ │ │
│ │ Node 0 (contig_page_data) │ │
│ │ ├── Zone DMA │ │
│ │ ├── Zone Normal │ │
│ │ └── Zone Movable │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

所有 CPU 访问内存的延迟相同

NUMA (Non-Uniform Memory Access) - 非均匀内存访问

定义:处理器访问本地内存的速度快于访问远程内存。

特点

  • 多个节点 (Node),每个节点包含 CPU 和本地内存
  • 节点间通过互联总线连接 (QPI, HyperTransport, Infinity Fabric 等)
  • 存在本地内存 (快速访问) 和远程内存 (慢速访问)
  • 需要考虑内存局部性优化

典型应用

  • 大型服务器 (2 路、4 路、8 路及以上)
  • 高性能计算 (HPC) 集群
  • 虚拟化主机

优缺点

  • ✅ 扩展性好,支持大量 CPU 和内存
  • ✅ 每个 CPU 有专属内存带宽
  • ❌ 编程复杂,需考虑内存亲和性
  • ❌ 远程内存访问存在延迟惩罚

NUMA 架构示意图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────┐
│ NUMA 架构 (多节点系统) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Node 0 Node 1 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ CPU0 │ │ CPU2 │ │
│ │ CPU1 │ │ CPU3 │ │
│ │ │ │ │ │
│ │ 本地内存 │◄─────► │ 本地内存 │ │
│ │ (快速访问) │ QPI/HT │ (快速访问) │ │
│ │ │ 互联总线 │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ ▲ │ ▲ │
│ └───────┴────────────────────────┴───────┘ │
│ 互联网络 (Interconnect) │
│ │
│ 访问 Node 0 内存: CPU0, CPU1 ≈ 80ns │
│ 访问 Node 1 内存: CPU0, CPU1 ≈ 120-150ns (跨节点) │
│ │
└─────────────────────────────────────────────────────────────┘

Linux 内核中的统一处理

关键设计:Linux 使用相同的数据结构 (pglist_data) 统一处理 UMA 和 NUMA,通过条件编译实现差异。

内存模型类型

1
2
3
4
// 三种内存模型 (Linux 6.12)
CONFIG_FLATMEM // 平坦内存模型 (UMA 系统使用)
CONFIG_SPARSEMEM // 稀疏内存模型 (NUMA 系统使用)
CONFIG_DISCONTIGMEM // 不连续内存模型 (已废弃)

内核源码位置

  • mm/memblock.c:100 - UMA 的 contig_page_data 定义
  • include/linux/mmzone.h:1580 - NODE_DATA() 宏定义
  • include/linux/numa.h:33 - NUMA 的 node_data[] 声明

2.1.2 pglist_data 结构

位置: include/linux/mmzone.h:1298

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
typedef struct pglist_data {
/*
* node_zones 包含当前节点的所有 zone。
* 不是所有 zone 都会被填充,但这是完整的列表。
*/
struct zone node_zones[MAX_NR_ZONES];

/*
* node_zonelists 包含所有节点的所有 zone 引用。
* 用于分配时的内存区域回退策略。
*/
struct zonelist node_zonelists[MAX_ZONELISTS];

int nr_zones; /* 当前节点填充的 zone 数量 */

#ifdef CONFIG_FLATMEM
struct page *node_mem_map; /* 节点的页面数组 */
#endif

#if defined(CONFIG_MEMORY_HOTPLUG) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT)
spinlock_t node_size_lock; /* 保护 node_start_pfn 等字段 */
#endif

unsigned long node_start_pfn; /* 节点起始页面帧号 */
unsigned long node_present_pages; /* 物理页面总数 */
unsigned long node_spanned_pages; /* 物理页面范围(包含空洞) */
int node_id; /* 节点 ID */

/* kswapd 等待队列 - 内核换页线程 */
wait_queue_head_t kswapd_wait;
wait_queue_head_t pfmemalloc_wait;

/* 为不同原因限制回收的工作队列 */
wait_queue_head_t reclaim_wait[NR_VMSCAN_THROTTLE];

atomic_t nr_writeback_throttled; /* 写回限制的任务数 */
unsigned long nr_reclaim_start; /* 限制开始时写入的页面数 */

#ifdef CONFIG_MEMORY_HOTPLUG
struct mutex kswapd_lock;
#endif

struct task_struct *kswapd; /* kswapd 内核线程 */
int kswapd_order; /* kswapd 正在回收的 order */
enum zone_type kswapd_highest_zoneidx;

int kswapd_failures; /* 'reclaimed == 0' 的次数 */

#ifdef CONFIG_COMPACTION
int kcompactd_max_order;
enum zone_type kcompactd_highest_zoneidx;
wait_queue_head_t kcompactd_wait;
struct task_struct *kcompactd; /* 内存压缩内核线程 */
bool proactive_compact_trigger;
#endif

/*
* 每个节点的保留页面,不可用于用户空间分配
*/
unsigned long totalreserve_pages;

#ifdef CONFIG_NUMA
/*
* 当存在更多未映射页面时,节点回收变得活跃
*/
unsigned long min_unmapped_pages;
unsigned long min_slab_pages;
#endif

#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
/*
* 如果大机器上的内存初始化被延迟,
* 这是需要初始化的第一个 PFN
*/
unsigned long first_deferred_pfn;
#endif

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
struct deferred_split deferred_split_queue;
#endif

#ifdef CONFIG_NUMA_BALANCING
unsigned int nbp_rl_start; /* 当前限制周期的开始时间(ms) */
unsigned long nbp_rl_nr_cand; /* 开始时的候选页面数 */
unsigned int nbp_threshold; /* 提升阈值(ms) */
#endif

/* 注意:如果启用了 MEMCG,这个字段未使用 */
struct lruvec __lruvec;

unsigned long flags; /* 节点标志 */

#ifdef CONFIG_LRU_GEN
struct lru_gen_mm_walk mm_walk; /* kswap mm walk 数据 */
struct lru_gen_memcg memcg_lru; /* lru_gen_folio 列表 */
#endif

/* 每节点 vmstat */
struct per_cpu_nodestat __percpu *per_cpu_nodestats;
atomic_long_t vm_stat[NR_VM_NODE_STAT_ITEMS];

#ifdef CONFIG_NUMA
struct memory_tier __rcu *memtier;
#endif

#ifdef CONFIG_MEMORY_FAILURE
struct memory_failure_stats mf_stats;
#endif
} pg_data_t;

2.1.3 节点状态

1
2
3
4
5
6
7
8
9
enum node_states {
N_POSSIBLE, // 节点可能存在
N_ONLINE, // 节点在线
N_NORMAL_MEMORY, // 普通内存
N_HIGH_MEMORY, // 高端内存 (有 DMA)
N_MEMORY, // 包含内存
N_CPU, // 包含 CPU
NR_NODE_STATES
};

2.1.4 UMA/NUMA 内核代码实现

UMA 系统的实现

在 UMA 系统中,内核使用单一的 pglist_data 结构来管理所有内存。

**contig_page_data 定义 (mm/memblock.c:100)**:

1
2
3
4
#ifndef CONFIG_NUMA
struct pglist_data __refdata contig_page_data;
EXPORT_SYMBOL(contig_page_data);
#endif

**UMA 的 NODE_DATA() 宏 (include/linux/mmzone.h:1580)**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef CONFIG_NUMA

extern struct pglist_data contig_page_data;

/*
* UMA 系统:所有节点访问返回同一个 pglist_data
* 这样 NUMA 和 UMA 系统可以使用相同的代码路径
*/
static inline struct pglist_data *NODE_DATA(int nid)
{
return &contig_page_data;
}

#endif /* !CONFIG_NUMA */

关键特性

  • 无论传入什么 nid,总是返回 &contig_page_data
  • 只有一个 Node (Node 0)
  • node_states[N_ONLINE] 只包含 Node 0

NUMA 系统的实现

在 NUMA 系统中,每个节点有独立的 pglist_data 结构。

**node_data 数组声明 (include/linux/numa.h:33)**:

1
2
3
4
5
6
7
8
#ifdef CONFIG_NUMA
#include <asm/sparsemem.h>

extern struct pglist_data *node_data[];
#define NODE_DATA(nid) (node_data[nid])

void __init alloc_node_data(int nid);
void __init alloc_offline_node_data(int nid);

NUMA 的节点初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// mm/mm_init.c 或特定架构的 init 代码
void __init alloc_node_data(int nid)
{
struct pglist_data *pgdat;

// 为每个节点分配 pglist_data 结构
pgdat = memblock_alloc_node(sizeof(*pgdat), SMP_CACHE_BYTES, nid);

// 初始化节点的各种字段
pgdat->node_id = nid;
// ... 更多初始化

// 保存到 node_data 数组
NODE_DATA(nid) = pgdat;
}

关键特性

  • node_data[] 是指针数组,每个元素指向一个节点的 pglist_data
  • NODE_DATA(nid) 返回对应节点的结构
  • MAX_NUMNODESCONFIG_NODES_SHIFT 决定(默认 0,最大 11)

内存模型详解

Linux 6.12 支持三种内存模型,用于描述物理内存到 struct page 数组的映射:

1. FLATMEM (平坦内存模型)

1
2
3
4
// 配置选项
CONFIG_FLATMEM y
# CONFIG_SPARSEMEM is not set
# CONFIG_FLAT_NODE_MEM_MAP is not set

特点

  • 物理内存是连续的 (或近似连续)
  • 使用单一的 mem_map 数组
  • pfn_to_page(page_nr) 简单的线性映射

实现

1
2
3
4
5
// FLATMEM 的页面映射
#define pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)

struct page *mem_map; // 全局的页面数组

适用场景:UMA 系统、内存连续的简单系统


2. SPARSEMEM (稀疏内存模型)

1
2
3
4
5
// 配置选项
CONFIG_SPARSEMEM y
CONFIG_SPARSEMEM_EXTREME y // 支持超大内存
CONFIG_SPARSEMEM_VMEMMAP y // 使用虚拟地址映射 memmap
CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOPOLOGY n

特点

  • 支持内存空洞和不连续内存
  • 内存按 section (通常是 128MB) 划分
  • 只有存在物理内存的 section 才分配 struct page 数组
  • NUMA 系统的默认模型

数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
// include/linux/mmzone.h
struct mem_section {
/*
* 该 section 的页面数组 (struct page 数组)
* 只有 section 存在时才分配
*/
unsigned long section_mem_map;

/*
* 热插拔相关字段
*/
struct page *section_root_page;
};

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// SPARSEMEM 的页面映射
#define pfn_to_section(pfn) \
((pfn) >> PFN_SECTION_SHIFT)

#define section_to_mem_map(ms) \
((struct page *)((ms)->section_mem_map & ~SECTION_MARKED_PRESENT))

#define __pfn_to_page(pfn) \
section_to_mem_map(__pfn_to_section(pfn))

#define __page_to_pfn(pg) \
(__section_to_pfn(__page_to_section(pg)) + \
((unsigned long)((pg) - section_to_mem_map(__page_to_section(pg)))))

Section 大小

1
2
3
4
5
// 由 SECTION_SIZE_BITS 决定,架构相关
// x86_64: SECTION_SIZE_BITS = 27 (128MB per section)
// ARM64: SECTION_SIZE_BITS = 27 (128MB per section)

#define PAGES_PER_SECTION (1UL << (SECTION_SIZE_BITS - PAGE_SHIFT))

适用场景:NUMA 系统、支持内存热插拔的系统、大内存系统


3. DISCONTIGMEM (不连续内存模型)

1
2
// 已废弃,不再推荐使用
CONFIG_DISCONTIGMEM

特点

  • 历史遗留模型,用于支持不连续物理内存
  • 已被 SPARSEMEM 完全替代
  • Linux 6.12 中仍存在但已标记为废弃

节点与内存模型的对应关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────┐
│ 节点与内存模型关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ UMA 系统: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 内存模型: FLATMEM │ │
│ │ 节点数: 1 (Node 0) │ │
│ │ 数据结构: contig_page_data (单一 pglist_data) │ │
│ │ 页面映射: mem_map (单一数组) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ NUMA 系统: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 内存模型: SPARSEMEM │ │
│ │ 节点数: 2+ (Node 0, Node 1, ...) │ │
│ │ 数据结构: node_data[] (pglist_data 指针数组) │ │
│ │ 页面映射: mem_section[][] (二维数组) │ │
│ │ │ │
│ │ Node 0 ──▶ pglist_data ──▶ zones ──▶ pages │ │
│ │ Node 1 ──▶ pglist_data ──▶ zones ──▶ pages │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

常用 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 获取节点数据
static inline struct pglist_data *NODE_DATA(int nid);

// 获取节点的起始 PFN
#define node_start_pfn(nid) (NODE_DATA(nid)->node_start_pfn)

// 获取节点的页面总数
#define node_present_pages(nid) (NODE_DATA(nid)->node_present_pages)

// 获取节点的 spanned 页面数 (包含空洞)
#define node_spanned_pages(nid) (NODE_DATA(nid)->node_spanned_pages)

// 获取节点的某个 zone
static inline struct zone *node_zone(int nid, enum zone_type zone_type)
{
return &NODE_DATA(nid)->node_zones[zone_type];
}

// 遍历所有在线节点
#define for_each_online_node(node) \
for ((node) = 0; (node) < MAX_NUMNODES; (node)++) \
if (node_online((node)))

// 检查节点是否在线
static inline int node_online(int nid)
{
return nid >= 0 && nid < MAX_NUMNODES &&
node_isset(nid, node_states[N_ONLINE]);
}

// CPU 到节点的映射
static inline int cpu_to_node(int cpu)
{
return early_cpu_to_node(cpu);
}

// 获取当前 CPU 的节点 ID
static inline int numa_node_id(void)
{
#ifdef CONFIG_USE_PERCPU_NUMA_NODE_ID
return __this_cpu_read(numa_node);
#else
return 0; // UMA 系统
#endif
}

2.2 内存区域 (Zone)

2.2.1 Zone 类型

为了解决不同硬件对内存访问的限制,Linux 将每个节点的内存划分为多个区域 (zone)。

位置: include/linux/mmzone.h:723

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
enum zone_type {
/*
* ZONE_DMA 和 ZONE_DMA32 用于某些外设无法 DMA 到所有可寻址内存的情况。
* 当这个区域覆盖整个 32 位地址空间时使用 ZONE_DMA32。
* ZONE_DMA 用于更小的 DMA 寻址限制。
*/
#ifdef CONFIG_ZONE_DMA
ZONE_DMA, // DMA 可访问区域 (<16MB)
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32, // 32位 DMA 可访问区域 (<4GB)
#endif
/*
* ZONE_NORMAL 是可寻址的正常内存。
* 如果 DMA 设备支持传输到所有可寻址内存,可以在 ZONE_NORMAL 上执行 DMA 操作。
*/
ZONE_NORMAL, // 正常内存区域
#ifdef CONFIG_HIGHMEM
/*
* ZONE_HIGHMEM 只能通过内核将部分映射到自己的地址空间来寻址。
* 例如 i386 使用它来让内核访问超过 900MB 的内存。
*/
ZONE_HIGHMEM, // 高端内存 (32位系统)
#endif
/*
* ZONE_MOVABLE 类似于 ZONE_NORMAL,但它包含可移动页面。
* 主要用途:
* 1. 使内存离线/拔出更有可能成功
* 2. 本地限制不可移动分配 - 例如增加 THP/巨页数量
*/
ZONE_MOVABLE, // 可迁移内存区域
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE, // 设备内存 (持久内存等)
#endif
__MAX_NR_ZONES
};

2.2.2 Zone 结构

位置: include/linux/mmzone.h:818

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
struct zone {
/* 只读字段 */

/* zone 水位,使用 *_wmark_pages(zone) 宏访问 */
unsigned long _watermark[NR_WMARK];
unsigned long watermark_boost;

unsigned long nr_reserved_highatomic;
unsigned long nr_free_highatomic;

/*
* 我们必须保留较低 zone 的一些内存,
* 否则冒着在较低 zone 上 OOM 的风险,尽管在更高 zone 有大量可释放内存。
*/
long lowmem_reserve[MAX_NR_ZONES];

#ifdef CONFIG_NUMA
int node;
#endif
struct pglist_data *zone_pgdat;
struct per_cpu_pages __percpu *per_cpu_pageset;
struct per_cpu_zonestat __percpu *per_cpu_zonestats;

/*
* high 和 batch 值被复制到各个 pageset 以加快访问速度
*/
int pageset_high_min;
int pageset_high_max;
int pageset_batch;

#ifndef CONFIG_SPARSEMEM
unsigned long *pageblock_flags; /* pageblock 的标志 */
#endif

unsigned long zone_start_pfn; /* zone 的起始 PFN */

/*
* spanned_pages 是 zone 覆盖的总页数(包括空洞)
* present_pages 是 zone 中存在的物理页面
* managed_pages 是 buddy 系统管理的页面数
* cma_pages 是分配给 CMA 使用的页面数
*/
atomic_long_t managed_pages;
unsigned long spanned_pages;
unsigned long present_pages;

#ifdef CONFIG_MEMORY_HOTPLUG
unsigned long present_early_pages;
#endif

#ifdef CONFIG_CMA
unsigned long cma_pages;
#endif

const char *name;

#ifdef CONFIG_MEMORY_ISOLATION
unsigned long nr_isolate_pageblock;
#endif

#ifdef CONFIG_MEMORY_HOTPLUG
seqlock_t span_seqlock;
#endif

int initialized;

/* 页面分配器使用的写密集字段 */
CACHELINE_PADDING(_pad1_);

/* 不同大小的空闲区域 */
struct free_area free_area[NR_PAGE_ORDERS];

#ifdef CONFIG_UNACCEPTED_MEMORY
struct list_head unaccepted_pages;
#endif

unsigned long flags;

spinlock_t lock; /* 主要保护 free_area */

/* 压缩和 vmstat 使用的写密集字段 */
CACHELINE_PADDING(_pad2_);

unsigned long percpu_drift_mark;

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
unsigned long compact_cached_free_pfn;
unsigned long compact_cached_migrate_pfn[ASYNC_AND_SYNC];
unsigned long compact_init_migrate_pfn;
unsigned long compact_init_free_pfn;
#endif

#ifdef CONFIG_COMPACTION
unsigned int compact_considered;
unsigned int compact_defer_shift;
int compact_order_failed;
#endif

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
bool compact_blockskip_flush;
#endif

bool contiguous;

CACHELINE_PADDING(_pad3_);
/* Zone 统计 */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
} ____cacheline_internodealigned_in_smp;

2.2.3 内存布局示例

x86_64 (64位系统):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────┐
│ 物理内存 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Node 0 (正常内存节点) │
│ ├── Zone DMA (0-16MB) - 用于 ISA DMA │
│ ├── Zone DMA32 (16MB-4GB) - 用于 32位 DMA │
│ ├── Zone Normal (4GB-128GB) - 正常内存区域 │
│ └── Zone Movable (可配置) - 可迁移内存 │
│ │
│ Node 1 (NUMA 节点,如果存在) │
│ ├── Zone Normal │
│ └── Zone Movable │
│ │
└─────────────────────────────────────────────────────────────┘

i386 (32位系统):

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────────────────────┐
│ 物理内存 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Zone DMA (0-16MB) │
│ Zone Normal (16MB-896MB) │
│ Zone HighMem (896MB-4GB, 需要动态映射) │
│ │
└─────────────────────────────────────────────────────────────┘

2.3 水位 (Watermark)

2.3.1 水位类型

水位是 Linux 内核用于判断是否需要启动页面回收的关键阈值。

位置: include/linux/mmzone.h:647

1
2
3
4
5
6
7
enum zone_watermarks {
WMARK_MIN, // 最小水位:系统紧急使用
WMARK_LOW, // 低水位:低于此值开始异步回收
WMARK_HIGH, // 高水位:低于此值开始同步回收
WMARK_PROMO, // 提升水位:用于多代 LRU
NR_WMARK
};

2.3.2 水位计算

1
2
3
4
5
6
7
8
9
10
static inline unsigned long wmark_pages(const struct zone *z,
enum zone_watermarks w)
{
return z->_watermark[w] + z->watermark_boost;
}

#define min_wmark_pages(z) wmark_pages(z, WMARK_MIN)
#define low_wmark_pages(z) wmark_pages(z, WMARK_LOW)
#define high_wmark_pages(z) wmark_pages(z, WMARK_HIGH)
#define promo_wmark_pages(z) wmark_pages(z, WMARK_PROMO)

2.3.3 水位示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

│ min low high promo
│ │ │ │ │
│ ▼ ▼ ▼ ▼

pages ─ ├─────────────────────────────────────────

│ ┌─────────┬────────┬─────────┬───────┐
│ │ min │ low │ high │ promo │
│ │ 临界 │ 警告 │ 正常 │ 提升 │
│ └─────────┴────────┴─────────┴───────┘



min : 最小保留页面 (系统紧急使用,GFP_ATOMIC 可用)
low : 低于此值唤醒 kswapd 异步回收
high : 低于此值同步回收,直到达到 high
promo : 多代 LRU 的提升水位

watermark_boost: 动态提升的水位值

2.3.4 水位相关 API

1
2
3
4
5
6
7
8
/* 检查水位是否 OK */
bool __zone_watermark_ok(struct zone *z, unsigned int order,
unsigned long mark, int highest_zoneidx,
unsigned int alloc_flags, long free_pages);

bool zone_watermark_ok(struct zone *z, unsigned int order,
unsigned long mark, int highest_zoneidx,
unsigned int alloc_flags);

2.4 物理页面 (Page)

2.4.1 page 结构

位置: include/linux/mm_types.h:72

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
struct page {
unsigned long flags; /* 原子标志,可能异步更新 */

union {
struct { /* 页缓存和匿名页面 */
union {
struct list_head lru; /* LRU 链表 */
struct {
void *__filler;
unsigned int mlock_count; /* mlock 计数 */
};
struct list_head buddy_list; /* 空闲页面 */
struct list_head pcp_list; /* Per-CPU 缓存 */
};

struct address_space *mapping; /* 页缓存映射 */
union {
pgoff_t index; /* 在映射中的偏移 */
unsigned long share; /* fsdax 共享计数 */
};

unsigned long private; /* 私有数据 */
};

struct { /* page_pool (网络栈使用) */
unsigned long pp_magic;
struct page_pool *pp;
unsigned long _pp_mapping_pad;
unsigned long dma_addr;
atomic_long_t pp_ref_count;
};

struct { /* 复合页的尾页面 */
unsigned long compound_head;
};

struct { /* ZONE_DEVICE 页面 */
struct dev_pagemap *pgmap;
void *zone_device_data;
};

struct rcu_head rcu_head;
};

union {
unsigned int page_type; /* 页面类型 */
atomic_t _mapcount; /* 页表映射计数 */
};

atomic_t _refcount; /* 引用计数 */

#ifdef CONFIG_MEMCG
unsigned long memcg_data; /* 内存控制组数据 */
#elif defined(CONFIG_SLAB_OBJ_EXT)
unsigned long _unused_slab_obj_exts;
#endif

#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* 内核虚拟地址 */
#endif

#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid; /* 最后访问的 CPU 和进程 */
#endif
} _struct_page_alignment;

2.4.2 页面标志

位置: include/linux/page-flags.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 页面状态标志 */
#define PG_locked 0 /* 页面锁定 */
#define PG_error 1 /* 页面错误 */
#define PG_referenced 2 /* 页面被引用 */
#define PG_uptodate 3 /* 页面最新 */
#define PG_dirty 4 /* 页面脏 */
#define PG_lru 5 /* 在 LRU 链表中 */
#define PG_active 6 /* 活跃页面 */
#define PG_slab 7 /* Slab 页面 */
#define PG_owner_priv_1 8 /* 所有者私有 */
#define PG_arch_1 9 /* 架构私有 */
#define PG_reserved 10 /* 保留页面 */
#define PG_private 11 /* 私有数据 */
#define PG_writeback 12 /* 正在回写 */
#define PG_head 14 /* 复合页首页 */
#define PG_swapcache 15 /* 在交换缓存中 */
#define PG_mappedtodisk 16 /* 已映射到磁盘 */
#define PG_reclaim 17 /* 正在回收 */
#define PG_swapbacked 18 /* 有交换后端 */
#define PG_unevictable 19 /* 不可驱逐 */

2.5 复合页与 Folio

2.5.1 复合页结构

Linux 使用复合页来管理多个连续的物理页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌────────────────────────────────────────────────────────────┐
│ 复合页 (order = 2) │
│ 4KB × 4 = 16KB │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│ │ 页面 0 │ 页面 1 │ 页面 2 │ 页面 3 │ │
│ │ (PG_head) │ (PG_tail) │ (PG_tail) │ (PG_tail) │ │
│ │ │ │ │ │ │
│ │ compound │ │ │ │ │
│ │ _order = 2 │ │ │ │ │
│ │ │ │ │ │ │
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
│ │ ▲ ▲ ▲ │
│ └───────────┴──────────────┴──────────────┘ │
│ compound_head 指向页面 0 │
└────────────────────────────────────────────────────────────┘

2.5.2 Folio 结构

Linux 5.15+ 引入 folio 结构来更好地管理多页复合页。

位置: include/linux/mm_types.h:324

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
struct folio {
union {
struct {
unsigned long flags;
union {
struct list_head lru;
struct {
void *__filler;
unsigned int mlock_count;
};
};
struct address_space *mapping;
pgoff_t index;
union {
void *private;
swp_entry_t swap;
};
atomic_t _mapcount;
atomic_t _refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
void *virtual;
int _last_cpupid;
};
struct page page;
};
union {
struct {
unsigned long _flags_1;
unsigned long _head_1;
atomic_t _large_mapcount;
atomic_t _entire_mapcount;
atomic_t _nr_pages_mapped;
atomic_t _pincount;
#ifdef CONFIG_64BIT
unsigned int _folio_nr_pages;
#endif
};
struct page __page_1;
};
union {
struct {
unsigned long _flags_2;
unsigned long _head_2;
void *_hugetlb_subpool;
void *_hugetlb_cgroup;
void *_hugetlb_cgroup_rsvd;
void *_hugetlb_hwpoison;
};
struct {
unsigned long _flags_2a;
unsigned long _head_2a;
struct list_head _deferred_list;
};
struct page __page_2;
};
};

2.5.3 Folio 操作 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 页面到 folio 转换 */
static inline struct folio *page_folio(struct page *page);

/* 获取 folio 的页面数 */
static inline unsigned int folio_nr_pages(struct folio *folio);

/* 获取 folio 的 order */
static inline unsigned int folio_order(struct folio *folio);

/* 分配 folio */
struct folio *folio_alloc(gfp_t gfp, unsigned int order);
struct folio *__folio_alloc_node(gfp_t gfp, unsigned int order, int nid);

/* 释放 folio */
void folio_put(struct folio *folio);


2.6 页面生命周期状态机

2.6.1 页面状态转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌─────────────────────────────────────────────────────────────────────┐
│ 页面生命周期状态机 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 分配 首次访问 被引用 回收 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ FREE │ ───▶ │ NEW │ ─────▶ │ACTIVE│ ───▶ │INACTIVE│ │
│ │ │ 无 │ │ mark_ │ │ 时间 │ │ │
│ │ │ 映射 │ │ page_ │ │ 流逝 │ │ │
│ │ │ │ │ accessed │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ ▲ │ ▲ │ │
│ │ │ │ ▼ │
│ │ 释放/回收 │ ┌──────┐ │
│ │ │ │ │SWAP │ │
│ └────────────────┴─────────────────┴──────────│ │ │
│ │ cache│ │
│ └──────┘ │
│ │ │
│ ▼ │
│ ┌──────┐ │
│ │ FREE │◀──────┘
│ └──────┘ │
│ │
│ 状态说明: │
│ ──────── │
│ FREE : 在 Buddy System 的 free_area 中 │
│ NEW : 刚分配,未加入 LRU │
│ ACTIVE : 被频繁访问,在 LRU_ACTIVE 链表中 │
│ INACTIVE : 很少访问,在 LRU_INACTIVE 链表中 │
│ SWAP : 被 swap 到磁盘 │
│ │
│ 相关章节: │
│ ───────── │
│ • ch02 - 物理内存模型 (page 结构) │
│ • ch03 - Buddy System (free_area 管理) │
│ • ch07 - 页面分配 (alloc_pages) │
│ • ch10 - 页面回收 (LRU 管理, kswapd) │
│ • ch12 - Swap 机制 │
│ │
└─────────────────────────────────────────────────────────────────────┘

2.6.2 页面状态转换操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 激活页面 - 从 INACTIVE 转为 ACTIVE */
static inline void mark_page_accessed(struct page *page)
{
if (!PageActive(page) && !PageUnevictable(page) && PageLRU(page))
activate_page(page);
}

/* 标记为非活跃 - 从 ACTIVE 转为 INACTIVE */
static inline void lru_cache_add_inactive(struct page *page)
{
if (!PageUnevictable(page))
lru_cache_add(page);
}

/* 标记为不可驱逐 */
void mark_page_unevictable(struct page *page);
void clear_page_unevictable(struct page *page);

/* 释放页面 - 回到 FREE 状态 */
void __free_pages(struct page *page, unsigned int order);

2.7 本章小结与跨章节关联

2.7.1 本章要点

本章介绍了 Linux 6.12 的物理内存模型:

  1. UMA vs NUMA 架构

    • UMA (均匀内存访问):单一内存空间,所有 CPU 访问延迟相同,使用 contig_page_data
    • NUMA (非均匀内存访问):多节点架构,本地内存访问快于远程,使用 node_data[] 数组
  2. **内存节点 (Node)**:pglist_data 结构,通过 NODE_DATA(nid) 访问

  3. 内存模型

    • FLATMEM:平坦内存模型,UMA 系统使用
    • SPARSEMEM:稀疏内存模型,NUMA 系统使用,支持内存热插拔
    • DISCONTIGMEM:已废弃
  4. **内存区域 (Zone)**:解决硬件访问限制,DMA、Normal、HighMem、Movable、Device

  5. **水位 (Watermark)**:min、low、high、promo 四个水位控制页面回收

  6. **物理页面 (Page)**:struct page 描述符,flags、refcount、mapcount

  7. 复合页与 Folio:多页管理,folio 是 Linux 5.15+ 的新抽象

  8. 页面生命周期:从分配到回收的完整状态转换

2.7.2 与其他章节的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌────────────────────────────────────────────────────────────┐
│ 本章内容 (物理内存模型) │
├────────────────────────────────────────────────────────────┤
│ │
│ Node/Zone/Page ──▶ ch03:Buddy System分配器 │
│ (如何使用这些结构分配内存) │
│ │
│ Page 结构 ──────▶ ch07:页面分配 (alloc_pages返回什么) │
│ │
│ watermark ──────▶ ch10:页面回收 (何时触发回收) │
│ │
│ zone ────────────▶ ch04:虚拟地址空间 (如何映射到zone) │
│ │
│ folio ──────────▶ ch12:页缓存 (folio在缓存中的使用) │
│ │
│ 页面生命周期 ────▶ ch09:缺页处理 (页面状态的触发) │
│ │
└────────────────────────────────────────────────────────────┘

关键关联路径:

  1. 分配路径: ch02(物理内存模型)ch03(Buddy分配器)ch07(alloc_pages API)

  2. 回收路径: ch02(watermark检查)ch10(kswapd/直接回收)ch02(FREE状态)

  3. 映射路径: ch02(Page结构)ch05(页表管理)ch06(VMA)ch09(缺页处理)

下一章将介绍物理内存分配器 (Buddy System)。

  • Title: Linux内核分析之内存管理-02
  • Author: 韩乔落
  • Created at : 2026-01-20 21:38:51
  • Updated at : 2026-02-24 14:04:44
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-02/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments