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

韩乔落

Linux 6.12 内存管理概览

基于 Linux 6.12.38 源码,涵盖物理内存与虚拟内存管理的核心机制


1. 概述

1.1 内存管理子系统职责

核心功能:

  • 物理内存管理:页面的分配、释放、回收
  • 虚拟内存管理:地址空间的创建、映射、保护
  • 页表管理:多级页表的创建、更新、遍历
  • 缺页处理:按需加载、写时复制
  • 页面回收:LRU 算法、交换机制
  • 内存共享:进程间共享内存、写时复制

1.2 内存管理架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌────────────────────────────────────────────────────────────────┐
│ 内存管理子系统 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ 页面分配器 │ │ Slab/Slub │ │ 页表管理 │ │
│ │ Buddy System│ │ Allocator │ │ Paging │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ 页面回收 │ │ 缺页处理 │ │ 内存映射 │ │
│ │ Page Reclaim│ │ Page Fault │ │ Memory Map │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ 交换机制 │ │ 内存压缩 │ │ 内存 cgroup │ │
│ │ Swap │ │ Compaction │ │ Memcg │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

1.3 关键源码位置

功能 目录 主要文件
核心内存管理 mm/ memory.c, page_alloc.c
页表管理 mm/ pgtable.c, fault.c, mmap.c
Slab 分配器 mm/ slab.c, slub.c, slob.c
页面回收 mm/ vmscan.c
交换机制 mm/ swap.c, swap_state.c, swapfile.c
内存压缩 mm/ compaction.c
页缓存 mm/ filemap.c, page-io.c
匿名内存 mm/ mprotect.c, mlock.c
内存 cgroup mm/ memcontrol.c
KSM mm/ ksm.c
页迁移 mm/ migrate.c
内核页表 arch/*/mm/ pgtable.c, init.c
头文件 include/linux/ mm.h, mm_types.h, mmzone.h, pgtable.h
体系相关 arch/*/mm/ 各种体系结构相关文件

1.4 相关数据结构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
struct page;                  // 物理页面描述符
struct folio; // 多页复合页描述符
struct zone; // 内存区域管理
struct pglist_data; // 节点管理 (NUMA)
struct vm_area_struct; // 虚拟内存区域
struct mm_struct; // 内存描述符
struct vm_struct; // 内核虚拟内存区域
struct address_space; // 地址空间 (页缓存)
struct mem_cgroup; // 内存控制组
struct kmem_cache; // Slab 缓存
struct reclaim_state; // 回收状态
struct shrinker; // 缓存收缩器
struct swap_info_struct; // 交换分区信息

2. 物理内存模型

2.1 内存节点 (Node)

NUMA 架构:

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
// 节点定义 (include/linux/mmzone.h)
typedef struct pglist_data {
// 节点状态
enum node_states {
N_POSSIBLE, // 节点可能存在
N_ONLINE, // 节点在线
N_NORMAL_MEMORY, // 普通内存
N_HIGH_MEMORY, // 高端内存 (有 DMA)
N_MEMORY, // 包含内存
N_CPU, // 包含 CPU
NR_NODE_STATES
};

// 节点 ID
int node_id;

// 内存区域
struct zone node_zones[MAX_NR_ZONES];

// 节点内存统计
struct per_cpu_nodestat __percpu *per_cpu_nodestats;

// 页面回收状态
struct wait_queue_head kswapd_wait;

// 更多字段...
} pg_data_t;

节点状态:

1
2
3
4
5
6
7
8
9
Node 0 (正常内存节点)
├── Zone DMA (0-16MB)
├── Zone DMA32 (16MB-4GB)
├── Zone Normal (4GB-128GB)
└── Zone HighMem (>128GB, 仅 32位)

Node 1 (NUMA 节点)
├── Zone Normal
└── Zone Movable

2.2 内存区域 (Zone)

区域类型:

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
enum zone_type {
ZONE_DMA, // DMA 可访问区域 (<16MB)
ZONE_DMA32, // 32位 DMA 可访问区域 (<4GB)
ZONE_NORMAL, // 正常内存区域
ZONE_HIGHMEM, // 高端内存 (32位系统)
ZONE_MOVABLE, // 可迁移内存区域
ZONE_DEVICE, // 设备内存 (持久内存等)
__MAX_NR_ZONES
};

struct zone {
// 水印 (watermark)
unsigned long _watermark[NR_WMARK];
unsigned long watermark_boost;

// 跨区域访问统计
unsigned long nr_scan_active;
unsigned long nr_scan_inactive;

// LRU 链表
struct list_head active_list_lru;
struct list_head inactive_list_lru;

// 空闲页面链表
struct free_area free_area[MAX_ORDER];

// 页面统计
unsigned long vm_stat[NR_VM_ZONE_STAT_ITEMS];

// 更多字段...
};

水位 (Watermark):

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

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

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

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




min : 最小保留页面 (系统紧急使用)
low : 低于此值开始异步回收
high : 低于此值开始同步回收

2.3 物理页面 (Page)

位置: include/linux/mm_types.h

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
struct page {
// 页面标志
unsigned long flags;

// 引用计数
atomic_t _refcount;

// 页面映射计数
atomic_t _mapcount;

// 页面索引 (在文件中的偏移)
unsigned long index;

// 复合页信息
struct {
unsigned long compound_head;
unsigned int compound_order;
};

// 联合: 根据页面用途不同而不同
union {
struct { // 页缓存页面
struct address_space *mapping;
pgoff_t index;
};

struct { // 匿名页面 / KSM
unsigned long swap_cache_entry;
};

struct { // 私有数据
unsigned long private;
};

struct { // slab 页面
struct kmem_cache *slab_cache;
void *freelist;
};
};

// LRU 链表节点
struct list_head lru;

// 更多字段...
};

页面标志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 页面状态标志
#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.4 复合页 (Compound Page)

结构:

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

Folios (多页复合页):

1
2
3
4
5
6
7
8
9
// Linux 5.15+ 引入的 folio 结构
struct folio {
struct page page;
};

// folio 操作
struct folio *page_folio(struct page *page);
struct folio *folio_alloc(gfp_t gfp, unsigned int order);
void folio_put(struct folio *folio);

3. 物理内存分配器

3.1 Buddy System 概述

分配器原理:

  • 将物理内存按 2^n 次幂大小组织
  • 相邻大小的空闲块可以合并
  • 分割大块满足小块分配请求
  • 回收时合并相邻块

空闲区域数组:

1
2
3
4
5
6
7
8
9
10
struct zone {
struct free_area free_area[MAX_ORDER];
};

struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};

#define MAX_ORDER 11 // 最大分配: 2^11 * 4KB = 8MB

Buddy System 结构:

1
2
3
4
5
6
7
8
9
Order 0 (4KB)    : [P] [P] [P] [P] [P] [P] [P] [P] ...
Order 1 (8KB) : [B ] [B ] [B ] [B ] ...
Order 2 (16KB) : [B ] [B ] ...
Order 3 (32KB) : [B ] ...
...
Order 10 (4MB) : [B ]

P = 单页 (Page)
B = Buddy (合并块)

3.2 分配与释放

分配页面:

1
2
3
4
5
6
7
8
9
10
11
// 位置: mm/page_alloc.c

// 分配 2^order 个连续页面
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);

// 分配指定节点的页面
struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
unsigned int order);

// 分配并清零
struct page *alloc_pages_exact(size_t size, gfp_t gfp_mask);

分配流程:

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
┌────────────────────────────────────────────────────────────┐
│ alloc_pages(order=2) │
└────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 1. 确定迁移类型 (migrate type) │
│ - MOVABLE (可迁移页面) │
│ - RECLAIMABLE (可回收页面) │
│ - UNMOVABLE (不可移动页面) │
│ - ISOLATE (隔离页面) │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 从 fast path 开始分配 │
│ - 检查 free_area[order] │
│ - 如果有空闲块,直接返回 │
└──────────────────────────────────────┘

没有足够空间

┌──────────────────────────────────────┐
│ 3. 慢速路径: 唤醒 kswapd │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 从更大 order 分割 │
│ - 找到更大的空闲块 │
│ - 递归分割成需要的大小 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 直接回收 (direct reclaim) │
│ - 同步回收页面 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 6. OOM Killer (最后手段) │
└──────────────────────────────────────┘

释放页面:

1
2
3
4
5
// 释放页面
void __free_pages(struct page *page, unsigned int order);

// 释放并清零
void free_pages(unsigned long addr, unsigned int order);

释放流程:

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
free_pages(page, order=1)


┌──────────────────────────┐
│ 1. 检查页面有效性 │
│ - 检查引用计数 │
│ - 检查页面标志 │
└──────────────────────────┘


┌──────────────────────────┐
│ 2. 释放页面到 buddy │
│ - 放入 free_area[1] │
└──────────────────────────┘


┌──────────────────────────┐
│ 3. 检查 buddy 是否空闲 │
│ - buddy = page ^ 2^1 │
│ - 如果空闲,合并 │
└──────────────────────────┘


┌──────────────────────────┐
│ 4. 递归向上合并 │
│ - order 1 → order 2 │
│ - order 2 → order 3 │
│ - ... │
└──────────────────────────┘

3.3 GFP 标志

定义: include/linux/gfp.h

区域修饰符:

1
2
3
4
#define __GFP_DMA        0x01u   // 从 DMA 区域分配
#define __GFP_HIGHMEM 0x02u // 从高端内存分配
#define __GFP_DMA32 0x04u // 从 DMA32 区域分配
#define __GFP_MOVABLE 0x08u // 可迁移页面

行为修饰符:

1
2
3
4
5
6
7
8
#define __GFP_RECLAIM    0x10u   // 可以回收
#define __GFP_HIGH 0x20u // 高优先级 (__GFP_ATOMIC)
#define __GFP_IO 0x40u // 可以执行 I/O
#define __GFP_FS 0x80u // 可以调用文件系统
#define __GFP_ZERO 0x01u // 返回清零页面
#define __GFP_NORETRY 0x10u // 不重试
#define __GFP_NOWARN 0x20u // 不打印警告
#define __GFP_RETRY_MAYFAIL 0x40u // 重试但可能失败

常用组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以睡眠和回收
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)

// 原子上下文分配 (不睡眠)
#define GFP_ATOMIC (__GFP_HIGH)

// 用户空间分配
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)

// 高端内存分配
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)

// DMA 分配
#define GFP_DMA __GFP_DMA

3.4 Per-CPU 页面缓存

目的: 减少自旋锁竞争,提高分配性能

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Per-CPU 页面缓存
struct per_cpu_pages {
int count; // 页面数量
int high; // 高水位
int batch; // 批量大小
struct list_head list; // 页面链表
};

struct per_cpu_pageset {
struct per_cpu_pages pcp;
// 统计信息...
};

struct zone {
struct per_cpu_pageset __percpu *pageset;
};

分配流程 (带 PCP 缓存):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
alloc_pages(GFP_KERNEL, order=0)


┌──────────────────────────────────────┐
│ 1. 检查本地 PCP 缓存 │
│ - 如果有页面,直接返回 │
└──────────────────────────────────────┘



┌──────────────────────────────────────┐
│ 2. 从 buddy 批量分配 (batch=64) │
│ - 加载到本地 PCP │
│ - 返回一个页面 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. PCP 缓存管理 │
│ - 超过 high: 批量释放到 buddy │
│ - 低于 batch: 批量从 buddy 获取 │
└──────────────────────────────────────┘

4. 虚拟地址空间

4.1 地址空间布局

x86_64 (48位虚拟地址):

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
┌─────────────────────────────────────────────────────────────────┐
│ 用户空间 (128TB) │
│ 0x0000000000000000 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 代码段 │ │ 数据段 │ │ 栈段 (向下增长) │ │
│ │ (Text) │ │ (Data/BSS) │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 内存映射段 (mmap 区域, 向上增长) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 栈 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 内核空间 (128TB) │
│ 0xffff800000000000 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 直接映射区 │ │ vmalloc区 │ │ 模块区 │ │
│ │ (Phys Map) │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 固定映射区 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

ARM64 (48位/52位虚拟地址):

1
2
用户空间 (TTBR0_EL1):    0x0000000000000000 - 0x0000ffffffffffff
内核空间 (TTBR1_EL1): 0xffff000000000000 - 0xffffffffffffffff

4.2 内存描述符 mm_struct

位置: include/linux/mm_types.h

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
struct mm_struct {
struct {
struct vm_area_struct *mmap; // VMA 链表头
struct rb_root mm_rb; // VMA 红黑树
unsigned long mmap_base; // mmap 基地址
unsigned long mmap_legacy_base; // 传统 mmap 基地址
unsigned long task_size; // 任务大小

// 页表
pgd_t * pgd; // 页全局目录
atomic_t mm_users; // 用户空间引用计数
atomic_t mm_count; // 总引用计数

// 地址空间
unsigned long total_vm; // 总页面数
unsigned long locked_vm; // 锁定页面数
unsigned long pinned_vm; // 固定页面数
unsigned long data_vm; // 数据段页面数
unsigned long exec_vm; // 执行段页面数
unsigned long stack_vm; // 栈页面数
unsigned long def_flags;

// 页表锁
spinlock_t page_table_lock;
struct rw_semaphore mmap_lock;

// 内存统计
unsigned long hiwater_rss; // 高水位 RSS
unsigned long hiwater_vm; // 高水位 VM
unsigned long total_vm; // 总 VM
unsigned long locked_vm; // 锁定 VM

// 更多字段...
};

// 内存状态
atomic_long_t nr_ptes; // 页表数量
atomic_long_t nr_pmds; // PMD 数量

// 缺页统计
unsigned long numa_next_scan;
unsigned long numa_scan_offset;

// 更多字段...
};

4.3 内核地址空间

直接映射区:

1
2
3
4
5
6
7
8
9
10
// 物理内存线性映射到内核空间
// x86_64: PAGE_OFFSET = 0xffff880000000000
// ARM64: PAGE_OFFSET = 0xffff800000000000

#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))
#define __pa(x) ((unsigned long)(x) - PAGE_OFFSET)

// 使用示例
void *virt_addr = __va(0x1000000); // 物理地址 16MB → 虚拟地址
unsigned long phys_addr = __pa(virt_addr); // 虚拟地址 → 物理地址

vmalloc 区:

1
2
3
4
5
6
7
8
// 非连续内存映射
void *vmalloc(unsigned long size);
void vfree(void *addr);

// 映射物理页到 vmalloc 区
void *vmap(struct page **pages, unsigned int count,
unsigned long flags, pgprot_t prot);
void vunmap(void *addr);

固定映射区:

1
2
3
4
5
6
7
8
9
10
// 编译时固定但虚拟地址可配置
enum fixed_addresses {
FIX_EARLYCON_MEM_BASE,
FIX_TEXT_POKE0,
FIX_TEXT_POKE1,
__end_of_fixed_addresses
};

extern void *__fix_to_virt(enum fixed_addresses idx);
#define FIX_ADDR(idx) __fix_to_virt(idx)

5. 页表管理

5.1 多级页表结构

x86_64 (4级页表):

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
┌─────────────────────────────────────────────────────────────────┐
│ 48位虚拟地址 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 63 47 │ 46-39 │ 38-30 │ 29-21 │ 20-12 │ 11-0 │
│ │ 符号扩展 │ PML4索引 │ PUD索引 │ PMD索引 │ PTE索引 │ 偏移 │
│ │ (全0/1) │ (9位) │ (9位) │ (9位) │ (9位) │ (12位) │
│ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ CR3 ──→ PML4 (Page Map Level 4) │
│ [0] ──→ │
│ [1] ──→ │
│ ... │
│ [511] ──→ PUD (Page Upper Directory) │
│ [0] ──→ │
│ [1] ──→ │
│ ... │
│ [511] ──→ PMD (Page Middle Directory) │
│ [0] ──→ │
│ [1] ──→ │
│ ... │
│ [511] ──→ PTE (Page Table Entry) │
│ [0] ──→ PFN │
│ [1] ──→ PFN │
│ ... │
│ [511] ──→ PFN │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ 物理页面 │ │
│ │ + 12位偏移 │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘

ARM64 (4级/5级页表):

1
2
3
4
5
// 48位 VA (4级)
PGD (Level 0) → PUD (Level 1) → PMD (Level 2) → PTE (Level 3) → 页面

// 52位 VA (5级, CONFIG_ARM64_VA_BITS_52)
PGD (Level 0) → PUD (Level 1) → PMD (Level 2) → PTE (Level 3) → 页面

5.2 页表项格式

PTE 格式 (x86_64):

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
┌─────────────────────────────────────────────────────────────────┐
│ Page Table Entry │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 63 52 │ 51 40 │ 39 12 │ 11 0 │
│ │ 保留/NI │ 页表属性 │ PFN │ 标志位 │
│ │ │ (索引/Key) │ │ │
│ │
└─────────────────────────────────────────────────────────────────┘

标志位:
┌────┬──────────────┬─────────────────────────────────────────┐
│ 位 │ 名称 │ 功能描述 │
├────┼──────────────┼─────────────────────────────────────────┤
│ 0 │ P (Present) │ 页面在内存中 │
│ 1 │ R/W │ 可写 │
│ 2 │ U/S │ 用户可访问 (0=仅内核) │
│ 3 │ PWT │ 直写缓存 │
│ 4 │ PCD │ 禁用缓存 │
│ 5 │ A (Accessed)│ 已访问 │
│ 6 │ D (Dirty) │ 已修改 │
│ 7 │ PS │ 页大小 (1=4MB/2MB, 0=4KB) │
│ 8 │ G (Global) │ 全局页面 (TLB不刷新) │
│ 9-11│ AVL │ 可用给软件使用 │
│ 11 │ PAT │ 页属性表索引 │
│ 52 │ XD │ 禁止执行 │
└────┴──────────────┴─────────────────────────────────────────┘

5.3 页表操作

创建页表:

1
2
3
4
5
6
// 创建页表
pgd_t *pgd_alloc(struct mm_struct *mm);
p4d_t *p4d_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address);
pud_t *pud_alloc(struct mm_struct *mm, p4d_t *p4d, unsigned long address);
pmd_t *pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long address);
pte_t *pte_alloc_map(struct mm_struct *mm, pmd_t *pmd, unsigned long address);

查找页表项:

1
2
3
4
5
6
7
8
9
10
11
12
// 查找 PTE
pte_t *pte_offset_map(pmd_t *pmd, unsigned long address);

// 查找 PMD
pmd_t *pmd_offset(pud_t *pud, unsigned long address);

// 查找 PUD
pud_t *pud_offset(p4d_t *p4d, unsigned long address);

// 查找 PGD
pgd_t *pgd_offset(struct mm_struct *mm, unsigned long address);
pgd_t *pgd_offset_k(unsigned long address); // 内核空间

设置页表项:

1
2
3
4
5
6
7
8
9
10
11
12
// 设置 PTE
set_pte_at(struct mm_struct *mm, unsigned long addr,
pte_t *ptep, pte_t pte);

// 清除 PTE
pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep);

// 设置页面映射
set_pte(pte_t *ptep, pte_t pte);

// 创建只读映射
set_pte_at(mm, addr, ptep, pfn_pte(pfn, PAGE_READONLY));

5.4 TLB 管理

TLB 刷新:

1
2
3
4
5
6
7
8
9
10
11
12
// 刷新单个地址
flush_tlb_page(struct vm_area_struct *vma, unsigned long addr);

// 刷新整个地址空间
flush_tlb_mm(struct mm_struct *mm);

// 刷新范围
flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end);

// 刷新所有 (内核使用)
flush_tlb_all();

TLB 操作:

1
2
3
; x86_64 TLB 指令
INVPCID ; 上下文特定的 TLB 失效
INVLPG ; 使单个 TLB 项失效

5.5 巨页

普通巨页 (Huge Pages):

1
2
3
4
5
6
7
8
9
10
11
12
13
// 2MB 巨页 (x86_64)
#define PMD_SHIFT 21 // 2MB
#define PUD_SHIFT 30 // 1GB

// 创建巨页映射
int huge_pmd_unshare(struct mm_struct *mm, unsigned long *addr,
pte_t *ptep);

// 巨页系统调用
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
// flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB
// 使用 MAP_HUGETLB 标志

透明巨页 (THP):

1
2
3
4
5
6
7
8
9
10
// THP 配置
#define HPAGE_PMD_SHIFT PMD_SHIFT
#define HPAGE_PMD_SIZE (1UL << HPAGE_PMD_SHIFT) // 2MB
#define HPAGE_PMD_MASK (~(HPAGE_PMD_SIZE - 1))

// THP 管理
bool transparent_hugepage_enabled(struct vm_area_struct *vma);
int hugepage_madvise(struct vm_area_struct *vma,
unsigned long *vm_flags, int advice);
void hugepage_vma_check(struct vm_area_struct *vma);

6. 虚拟内存区域 (VMA)

6.1 vm_area_struct 结构

位置: include/linux/mm_types.h

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
struct vm_area_struct {
// 红黑树节点
struct rb_node vm_rb;

// 链表节点 (按地址排序)
struct list_head vm_list;

// VMA 范围
unsigned long vm_start; // 起始地址
unsigned long vm_end; // 结束地址

// 页表权限
pgprot_t vm_page_prot; // 页保护

// VMA 标志
unsigned long vm_flags;

// 红黑树
struct rb_root mm_rb;
struct rw_semaphore mmap_lock;

// 关联
struct mm_struct *vm_mm; // 所属 mm_struct
pgd_t *vm_pgdir; // 页目录

// 文件映射
unsigned long vm_pgoff; // 文件偏移 (页为单位)
struct file *vm_file; // 映射文件
void *vm_private_data; // 私有数据

// 操作
const struct vm_operations_struct *vm_ops;

// 下一个 VMA (地址排序)
struct vm_area_struct *vm_next;

// 更多字段...
};

6.2 VMA 标志

定义: include/linux/mm.h

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
// VMA 标志
#define VM_READ 0x00000001 // 可读
#define VM_WRITE 0x00000002 // 可写
#define VM_EXEC 0x00000004 // 可执行
#define VM_SHARED 0x00000008 // 共享映射

#define VM_MAYREAD 0x00000010 // 可能可读
#define VM_MAYWRITE 0x00000020 // 可能可写
#define VM_MAYEXEC 0x00000040 // 可能可执行
#define VM_MAYSHARE 0x00000080 // 可能共享

#define VM_GROWSDOWN 0x00000100 // 向下增长 (栈)
#define VM_GROWSUP 0x00000200 // 向上增长
#define VM_PFNMAP 0x00000400 // PFN 映射
#define VM_DENYWRITE 0x00000800 // 拒绝写入
#define VM_IO 0x00004000 // IO 映射
#define VM_SHARED 0x00000008 // 共享
#define VM_MIXEDMAP 0x10000000 // 混合映射

#define VM_HUGETLB 0x00400000 // 巨页
#define VM_NONLINEAR 0x00800000 // 非线性映射
#define VM_ARCH_1 0x01000000 // 架构特定
#define VM_DONTDUMP 0x04000000 // 不 dump
#define VM_DOEXEC 0x10000000 // 允许执行
#define VM_SOFTDIRTY 0x20000000 // 软脏位
#define VM_MIXEDMAP 0x10000000 // 混合映射

6.3 VMA 操作

位置: include/linux/mm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct vm_operations_struct {
// 打开 VMA
void (*open)(struct vm_area_struct * area);

// 关闭 VMA
void (*close)(struct vm_area_struct * area);

// 缺页处理 (读)
vm_fault_t (*fault)(struct vm_fault *vmf);

// 缺页处理 (写)
vm_fault_t (*page_mkwrite)(struct vm_fault *vmf);

// 页面错误
vm_fault_t (*pfn_mkwrite)(struct vm_fault *vmf);

// 访问许可
vm_fault_t (*access)(struct vm_fault *vmf);

// 页面标记脏
vm_fault_t (*pmd_fault)(struct vm_fault *vmf, unsigned long address);

// 更多操作...
};

6.4 VMA 管理操作

查找 VMA:

1
2
3
4
5
6
7
8
9
10
11
// 查找包含地址的 VMA
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);

// 查找并锁定 VMA
struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,
unsigned long addr);

// 查找最近的 VMA
struct vm_area_struct *find_vma_prev(struct mm_struct *mm,
unsigned long addr,
struct vm_area_struct **pprev);

创建/删除 VMA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 插入 VMA
int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma);

// 创建 VMA
struct vm_area_struct *vm_area_alloc(struct mm_struct *mm);

// 释放 VMA
void vm_area_free(struct vm_area_struct *vma);

// 合并 VMA
struct vm_area_struct *vma_merge(struct mm_struct *mm,
struct vm_area_struct *prev,
unsigned long addr, unsigned long end,
unsigned long vm_flags,
pgprot_t vm_prot,
struct mempolicy *policy,
unsigned long vm_pgoff,
struct anon_vma *anon_vma,
struct file *file, pgoff_t vm_pgoff);

7. 页面分配

7.1 alloc_pages 函数族

函数列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 分配 2^order 个页面
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);

// 从指定节点分配
struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
unsigned int order);

// 从指定节点和区域分配
struct page *alloc_pages_exact_node(int nid, gfp_t gfp_mask,
unsigned int order);

// 分配并转换为内核虚拟地址
void *alloc_pages_exact(size_t size, gfp_t gfp_mask);
void free_pages_exact(void *virt, size_t size);

// 分配单页
struct page *alloc_page(gfp_t gfp_mask);

7.2 __get_free_pages

函数:

1
2
3
4
5
6
// 分配并返回内核虚拟地址 (直接映射区)
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

// 常用变体
unsigned long get_zeroed_pages(gfp_t gfp_mask, unsigned int order);
unsigned long __get_dma_pages(gfp_t gfp_mask, unsigned int order);

7.3 释放页面

函数:

1
2
3
4
5
6
7
8
9
10
11
12
// 释放页面
void __free_pages(struct page *page, unsigned int order);

// 释放虚拟地址对应的页面
void free_pages(unsigned long addr, unsigned int order);

// 检查页面是否可释放
bool free_pages_prepare(struct page *page, unsigned int order);

// 批量释放
void free_unref_page(struct page *page, unsigned int order);
void free_unref_page_list(struct list_head *list);

7.4 页面引用计数

操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 增加引用计数
void get_page(struct page *page);
struct page *grab_page(struct page *page);

// 减少引用计数
void put_page(struct page *page);

// 检查引用计数
bool page_ref_count(struct page *page);
bool page_ref_add_unless(struct page *page, int nr, int unless);

// 尝试获取引用
bool try_get_page(struct page *page);

8. Slab 分配器

8.1 Slab 分配器概述

设计目的:

  • 减少内存碎片
  • 提高分配速度
  • 缓存常用对象

三种实现:

  1. SLAB - 原始实现
  2. SLUB - 默认实现 (简化、性能更好)
  3. SLOB - 针对小型系统

8.2 kmem_cache 结构

位置: include/linux/slab_def.h

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
struct kmem_cache {
// 缓存配置
unsigned int object_size; // 对象大小
unsigned int size; // 实际分配大小 (含对齐)
unsigned int align; // 对齐要求
slab_flags_t flags; // 标志
unsigned long useroffset; // 用户偏移
unsigned long usersize; // 用户大小

// Slab 统计
unsigned int num; // 每个 slab 的对象数
unsigned int gfporder; // 分配阶数

// 对象操作
void (*ctor)(void *object); // 构造函数
void (*dtor)(void *object); // 析构函数

// 链表管理
struct list_head list; // 缓存链表

// Per-CPU 数据
struct kmem_cache_cpu __percpu *cpu_slab;

// 更多字段...
};

8.3 Slab 分配 API

创建/销毁缓存:

1
2
3
4
5
6
7
// 创建缓存
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,
slab_flags_t flags, void (*ctor)(void *));

// 销毁缓存
void kmem_cache_destroy(struct kmem_cache *s);

分配/释放对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 从缓存分配对象
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfp_flags);
void *kmem_cache_alloc_lru(struct kmem_cache *s, struct list_lru *lru,
gfp_t gfp_flags);

// 批量分配
int kmem_cache_alloc_bulk(struct kmem_cache *s, gfp_t flags, size_t size,
void **p);

// 释放对象
void kmem_cache_free(struct kmem_cache *s, void *obj);

// 批量释放
void kmem_cache_free_bulk(struct kmem_cache *s, size_t size, void **p);

通用分配器:

1
2
3
4
5
6
7
8
9
// 通用 kmalloc (大小必须 <= 2^(MAX_ORDER-1) 页面)
void *kmalloc(size_t size, gfp_t flags);
void *kzalloc(size_t size, gfp_t flags); // 分配并清零
void *kmalloc_array(size_t n, size_t size, gfp_t flags);
void *kcalloc(size_t n, size_t size, gfp_t flags); // 数组分配
void *krealloc(const void *p, size_t new_size, gfp_t flags);

// 释放
void kfree(const void *objp);

8.4 SLUB 实现

SLUB 特点:

  • 简化的 slab 实现
  • 更好的 per-CPU 缓存
  • 更少的内存开销

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct kmem_cache_cpu {
void **freelist; // 空闲对象链表
unsigned long tid; // CPU ID
struct page *page; // 当前 slab 页面
struct page *partial; // 部分 slab
unsigned int stat; // 统计
};

struct kmem_cache_node {
spinlock_t list_lock;
struct list_head partial; // 部分 slab 列表
struct list_head full; // 满 slab 列表
unsigned long nr_partial; // 部分 slab 数量
unsigned long nr_slabs; // 总 slab 数量
};

9. 缺页异常处理

9.1 缺页异常流程

x86_64 缺页异常:

1
2
3
4
5
6
7
; 页错误 (#PF) - 异常向量 14
; 错误码格式:
; bit 0 (P): 0 = 页不存在, 1 = 页保护违例
; bit 1 (W): 0 = 读操作, 1 = 写操作
; bit 2 (U): 0 = 监督模式, 1 = 用户模式
; bit 3 (R): 0 = 普通访问, 1 = 保留位访问
; bit 4 (I): 0 = 正常访问, 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
44
45
用户/内核访问无效地址


┌──────────────────────────────────────┐
│ CPU 触发 #PF 异常 │
│ 错误码推入栈 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ do_page_fault(regs, error_code, │
│ address) │
│ │
│ 1. 检查地址是否在内核空间 │
│ 2. 检查是否在临界区 │
│ 3. 查找对应的 VMA │
│ 4. 检查访问权限 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ handle_mm_fault(vma, address, │
│ flags, regs) │
│ │
│ 1. 检查 VMA 权限 │
│ 2. 调用特定处理函数 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 根据页面类型处理: │
│ │
│ ┌────────────────────────────────┐ │
│ │ 文件映射: filemap_fault │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 匿名映射: do_anonymous_page │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 写时复制: do_wp_page │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 堆栈扩展: expand_stack │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘

9.2 匿名页面缺页

位置: mm/memory.c:do_anonymous_page()

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
vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct page *page;
pte_t entry;

// 1. 检查 VMA 是否为匿名映射
if (vma->vm_file)
return VM_FAULT_SIGBUS;

// 2. 分配新页面 (如果是写操作)
if (vmf->flags & FAULT_FLAG_WRITE) {
page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,
vmf->address);
if (!page)
return VM_FAULT_OOM;
}

// 3. 创建 PTE
entry = mk_pte(page, vma->vm_page_prot);
if (vmf->flags & FAULT_FLAG_WRITE)
entry = pte_mkwrite(pte_mkdirty(entry));

// 4. 设置页表
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

return VM_FAULT_NOPAGE;
}

9.3 文件映射缺页

位置: mm/filemap.c:filemap_fault()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vm_fault_t filemap_fault(struct vm_fault *vmf)
{
struct address_space *mapping = file_mmap(vmf->vma->vm_file);
struct page *page;
pgoff_t offset = vmf->pgoff;

// 1. 检查页缓存
page = find_get_page(mapping, offset);
if (likely(page)) {
// 页面在缓存中
return VM_FAULT_LOCKED;
}

// 2. 页面不在缓存,从文件读取
page = page_cache_sync_readahead(mapping, ra,
vmf->vma, vmf->pgoff);
if (!page)
return VM_FAULT_OOM;

return VM_FAULT_LOCKED;
}

9.4 写时复制

位置: mm/memory.c:do_wp_page()

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
vm_fault_t do_wp_page(struct vm_fault *vmf)
__releases(vmf->ptl)
{
struct vm_area_struct *vma = vmf->vma;
struct page *page = vmf->page;
pte_t entry;

// 1. 检查页面是否是私有的
if (page_mapcount(page) == 1 && !PageAnon(page)) {
// 只有当前进程映射此页面,直接标记为可写
entry = pte_mkdirty(vmf->orig_pte);
entry = pte_mkwrite(entry);
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
return VM_FAULT_WRITE;
}

// 2. 多个进程共享,需要复制
if (PageAnon(page))
page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,
vmf->address);
else
page = alloc_page_vma(GFP_HIGHUSER, vma, vmf->address);

if (!page)
return VM_FAULT_OOM;

// 3. 复制页面内容
copy_user_highpage(page, vmf->page, vmf->address, vma);

// 4. 更新页表
__SetPageUptodate(page);
entry = mk_pte(page, vma->vm_page_prot);
entry = pte_mkwrite(pte_mkdirty(entry));
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

return VM_FAULT_WRITE;
}

9.5 堆栈扩展

位置: mm/mmap.c:expand_stack()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int expand_stack(struct vm_area_struct *vma, unsigned long address)
{
// 1. 检查是否是栈 VMA
if (!(vma->vm_flags & VM_GROWSDOWN))
return -ENOMEM;

// 2. 检查地址是否在合理范围内
if (address < vma->vm_start) {
// 向下扩展
unsigned long size = vma->vm_end - address;
if (size > RLIMIT_STACK)
return -ENOMEM;
}

// 3. 扩展 VMA
if (find_vma_intersection(vma->vm_mm, address, vma->vm_end))
return -ENOMEM;

vma->vm_start = address;
return 0;
}

10. 页面回收

10.1 LRU 链表

LRU 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum lru_list {
LRU_INACTIVE_ANON = 0, // 非活跃匿名页
LRU_ACTIVE_ANON, // 活跃匿名页
LRU_INACTIVE_FILE, // 非活跃文件页
LRU_ACTIVE_FILE, // 活跃文件页
LRU_UNEVICTABLE, // 不可驱逐页
NR_LRU_LISTS
};

struct zone {
struct list_head active_list_lru; // 活跃 LRU
struct list_head inactive_list_lru; // 非活跃 LRU
};

LRU 链表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────────────────────────────────────┐
│ LRU 链表 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 活跃匿名页: [A] ←→ [A] ←→ [A] ←→ [A] ←→ ... │
│ 非活跃匿名页: [I] ←→ [I] ←→ [I] ←→ [I] ←→ ... │
│ 活跃文件页: [F] ←→ [F] ←→ [F] ←→ [F] ←→ ... │
│ 非活跃文件页: [F] ←→ [F] ←→ [F] ←→ [F] ←→ ... │
│ 不可驱逐: [U] ←→ [U] ←→ [U] ←→ [U] ←→ ... │
│ │
└──────────────────────────────────────────────────────────────┘

A = Active (最近被访问)
I = Inactive (最近未被访问)
F = File page (文件映射)
U = Unevictable (mlock, swap cache 等)

10.2 页面状态转换

状态机:

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
┌──────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ 新分配 ┌────────────┐ │
│ │ 新页面 │ ─────────────→ │ INACTIVE │ │
│ │ │ │ (ANON/FILE) │ │
│ └──────────┘ └─────────────┘ │
│ │ │
│ │ 被引用 │
│ ▼ │
│ ┌────────────┐ │
│ │ ACTIVE │ │
│ │ (ANON/FILE) │ │
│ └─────────────┘ │
│ │ │
│ │ 时间流逝 │
│ ▼ │
│ ┌────────────┐ │
│ │ INACTIVE │ │
│ └─────────────┘ │
│ │ │
│ │ 释放 │
│ ▼ │
│ ┌────────────┐ │
│ │ 空闲 │ │
│ │ Free │ │
│ └────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘

10.3 kswapd 内核线程

位置: mm/vmscan.c:kswapd()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int kswapd(void *p)
{
struct pglist_data *pgdat = p;
struct task_struct *tsk = current;

// 设置调度策略
tsk->flags |= PF_MEMALLOC | PF_KSWAPD;
set_freezable();

while (!kthread_should_stop()) {
// 等待需要回收
wait_event_freezable(kswapd_wait,
kswapd_run(pgdat));

// 执行页面回收
kswapd_run(pgdat);

// 平衡节点
balance_pgdat(pgdat, order, highest_zoneidx);
}

return 0;
}

回收触发条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 检查是否需要唤醒 kswapd
static inline bool pgdat_watermark_ok(struct pglist_data *pgdat,
int classzone_idx)
{
int i;

for (i = 0; i <= classzone_idx; i++) {
struct zone *zone = pgdat->node_zones + i;
if (zone_watermark_ok(zone, order, 0, classzone_idx))
continue;
return false;
}
return true;
}

10.4 直接回收

位置: mm/vmscan.c:do_try_to_free_pages()

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
unsigned long do_try_to_free_pages(struct zonelist *zonelist,
int order,
gfp_t gfp_mask)
{
struct scan_control sc = {
.gfp_mask = gfp_mask,
.order = order,
.may_writepage = !laptop_mode,
.nr_to_reclaim = SWAP_CLUSTER_MAX,
.may_unmap = 1,
.may_swap = 1,
};

// 执行回收扫描
while (!sc.proactive) {
// 收缩 slab
shrink_slab(gfp_mask, order);

// 收缩 LRU
shrink_zones(zonelist, &sc);

// 检查是否完成
if (sc.nr_reclaimed >= sc.nr_to_reclaim)
break;
}

return sc.nr_reclaimed;
}

10.5 页面回收算法

扫描控制:

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
struct scan_control {
// 扫描目标
unsigned long nr_to_reclaim; // 目标回收页面数
gfp_t gfp_mask; // GFP 标志

// 扫描统计
unsigned long nr_scanned; // 已扫描页面数
unsigned long nr_reclaimed; // 已回收页面数

// 扫描选项
unsigned int order; // 分配阶数
int may_writepage; // 是否写回脏页
int may_unmap; // 是否取消映射
int may_swap; // 是否交换
int proactive; // 主动回收
};

// LRU 比例控制
static unsigned long get_scan_count(struct lruvec *lruvec,
struct scan_control *sc)
{
unsigned long file, anon;
unsigned long scan;

// 计算文件页和匿名页的比例
file = lruvec_page_state(lruvec, NR_INACTIVE_FILE) +
lruvec_page_state(lruvec, NR_ACTIVE_FILE);
anon = lruvec_page_state(lruvec, NR_INACTIVE_ANON) +
lruvec_page_state(lruvec, NR_ACTIVE_ANON);

// 根据交换压力调整扫描比例
scan = (file * sc->swappiness) / 200;
return scan;
}

11. 内存映射

11.1 mmap 系统调用

位置: mm/mmap.c:ksys_mmap_pgoff()

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
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}

static unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
int error;

// 1. 参数校验
if (!(flags & MAP_FIXED))
addr = round_hint_to_min(addr);

len = PAGE_ALIGN(len);
if (!len)
return -ENOMEM;

// 2. 查找未使用的虚拟地址区域
addr = get_unmapped_area(file, addr, len, pgoff, flags);
if (addr & ~PAGE_MASK)
return addr;

// 3. 创建 VMA
vma = vm_area_alloc(mm);
if (!vma)
return -ENOMEM;

vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;

// 4. 设置 VMA 操作
if (file)
vma->vm_ops = file->f_op->mmap;

// 5. 插入 VMA
error = mmap_region(file, addr, len, vm_flags, pgoff, vma);
if (error)
return error;

return addr;
}

11.2 mmap 标志

标志定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define MAP_SHARED       0x01    // 共享映射
#define MAP_PRIVATE 0x02 // 私有映射 (COW)
#define MAP_FIXED 0x10 // 固定地址映射
#define MAP_ANONYMOUS 0x20 // 匿名映射
#define MAP_GROWSDOWN 0x0100 // 向下增长 (栈)
#define MAP_DENYWRITE 0x0800 // 拒绝写入
#define MAP_EXECUTABLE 0x1000 // 可执行
#define MAP_LOCKED 0x2000 // 锁定内存
#define MAP_NORESERVE 0x4000 // 不保留交换空间
#define MAP_POPULATE 0x8000 // 预分配页面
#define MAP_NONBLOCK 0x10000 // 非阻塞
#define MAP_STACK 0x20000 // 映射用于栈
#define MAP_HUGETLB 0x40000 // 使用巨页

11.3 mprotect 系统调用

位置: mm/mprotect.c

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
SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
unsigned long, prot)
{
struct mm_struct *mm = current->mm;
unsigned long end, nstart, nend;
struct vm_area_struct *vma;

// 1. 参数校验
end = start + len;
if (end < start)
return -EINVAL;

// 2. 查找 VMA
vma = find_vma(mm, start);
if (!vma)
return -ENOMEM;

// 3. 修改保护
while (start < end) {
unsigned long newflags;

nstart = start;
nend = end;

// 分割 VMA
if (start > vma->vm_start)
nstart = start;
if (end < vma->vm_end)
nend = end;

// 计算新标志
newflags = calc_vm_prot_bits(prot, 0);
newflags |= (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));

// 更新 VMA
error = mprotect_fixup(vma, &prev, nstart, nend, newflags);
if (error)
return error;

start = nend;
vma = prev->vm_next;
}

return 0;
}

11.4 munmap 系统调用

位置: mm/mmap.c:vm_munmap()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int vm_munmap(unsigned long start, size_t len)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long end;

// 1. 参数校验
end = start + len;
if (end < start)
return -EINVAL;

// 2. 查找 VMA
while ((vma = find_vma(mm, start)) != NULL) {
// 删除 VMA
detach_vmas_to_be_unmapped(mm, vma, prev, end);

// 释放资源
unmap_region(mm, vma, prev, start, end);

break;
}

return 0;
}

12. 匿名内存与页缓存

12.1 匿名内存

特点:

  • 不与文件关联
  • 使用 swap 作为后备存储
  • 包括堆、栈、mmap(MAP_ANONYMOUS)

分配:

1
2
3
4
5
6
// brk 系统调用
SYSCALL_DEFINE2(brk, unsigned long, brk, unsigned long, arg);

// mmap 匿名映射
addr = mmap(NULL, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

缺页处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 位置: mm/memory.c:do_anonymous_page()
vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct page *page;

// 分配零页
page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vmf->address);
if (!page)
return VM_FAULT_OOM;

// 清零
clear_user_highpage(page, vmf->address);

// 建立映射
entry = mk_pte(page, vma->vm_page_prot);
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);

return VM_FAULT_NOPAGE;
}

12.2 页缓存

位置: mm/filemap.c

address_space 结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct address_space {
struct inode *host; // 所属 inode
struct xarray i_pages; // 页缓存基数树
gfp_t gfp_mask; // 分配标志
spinlock_t i_lock; // 内部锁
unsigned int i_pagesize; // 页大小

const struct address_space_operations *a_ops; // 操作

unsigned long flags; // 错误标志
errseq_t wb_err; // 写回错误

spinlock_t private_lock;
struct list_head private_list;
void *private_data;
};

页缓存操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 查找页面
struct page *find_get_page(struct address_space *mapping, pgoff_t offset);
struct page *pagecache_get_page(struct address_space *mapping,
pgoff_t offset,
int fgp_flags, gfp_t gfp_mask);

// 添加页面
void add_to_page_cache_lru(struct page *page, struct address_space *mapping,
pgoff_t index, gfp_t gfp);

// 删除页面
void delete_from_page_cache(struct page *page);

// 读取页面
struct page *read_cache_page(struct address_space *mapping,
pgoff_t index,
filler_t *filler, void *data);

12.3 页回写

位置: mm/page-writeback.c

回写触发:

1
2
3
4
5
6
7
// 当脏页达到阈值时触发
void balance_dirty_pages_ratelimited(struct address_space *mapping);
void balance_dirty_pages(struct address_space *mapping,
unsigned long throttle_threshold);

// 周期性回写
static int wb_kupdate(void *dummy);

脏页阈值:

1
2
3
4
5
6
7
# /proc/sys/vm 参数
/proc/sys/vm/dirty_background_ratio # 后台回写比例
/proc/sys/vm/dirty_background_bytes # 后台回写字节数
/proc/sys/vm/dirty_ratio # 脏页比例
/proc/sys/vm/dirty_bytes # 脏页字节数
/proc/sys/vm/dirty_expire_centisecs # 脏页过期时间
/proc/sys/vm/dirty_writeback_centisecs # 回写周期

13. 内存压缩与迁移

13.1 内存压缩

位置: mm/compaction.c

目的:

  • 减少内存碎片
  • 创建连续的大块内存
  • 满足高阶分配请求

压缩原理:

1
2
3
4
5
6
7
8
9
10
11
压缩前:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ │ B │ │ │ C │ D │ │ E │ │ F │ │ G │ │ │ H │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
使用 空闲 使用 空闲 空闲 使用 使用 空闲 使用 空闲 使用 空闲 空闲 空闲 使用

压缩后:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │ G │ H │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
使用 使用 使用 使用 使用 使用 使用 使用 空闲 空闲 空闲 空闲 空闲 空闲 空闲 空闲

压缩扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 位置: mm/compaction.c
static unsigned long compact_zone_order(struct zone *zone, int order,
gfp_t gfp_mask,
enum compact_priority prio)
{
struct compact_control cc = {
.nr_freepages = 0,
.nr_migratepages = 0,
.order = order,
.gfp_mask = gfp_mask,
.zone = zone,
.mode = (prio == COMPACT_PRIO_SYNC) ? MIGRATE_SYNC : MIGRATE_ASYNC,
.alloc_flags = alloc_flags,
};

// 执行压缩
compact_zone(&cc);

return cc.nr_freepages;
}

13.2 页迁移

位置: mm/migrate.c

用途:

  • NUMA 负载均衡
  • 内存热插拔
  • 页面迁移 (mbind, move_pages)

迁移流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 迁移单个页面
int migrate_pages(struct list_head *from, new_page_t get_new_page,
free_page_t put_new_page, unsigned long private,
enum migrate_mode mode, int reason);

// 迁移模式
enum migrate_mode {
MIGRATE_ASYNC, // 异步迁移
MIGRATE_SYNC, // 同步迁移
MIGRATE_SYNC_NO_COPY, // 同步但不复制
};

// 迁移原因
enum migrate_reason {
MR_COMPACTION, // 内存压缩
MR_MEMORY_FAILURE, // 内存错误
MR_SYSCALL, // 系统调用
MR_MEMPOLICY_MBIND,// 内存策略
MR_NUMA_MISPLACED, // NUMA 错位
// ...
};

13.3 kcompactd

位置: mm/compaction.c

内核线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int kcompactd(void *p)
{
struct pglist_data *pgdat = p;
struct compact_control cc;

set_freezable();

while (!kthread_should_stop()) {
// 等待工作
wait_event_freezable(pgdat->kcompactd_wait,
kcompactd_work_requested(pgdat));

// 执行压缩
for_each_zone(zone) {
if (!compaction_suitable(zone, order))
continue;

compact_zone(&cc);
}
}

return 0;
}

14. 内存控制组 (Memory Cgroup)

14.1 memcg 概述

用途:

  • 限制一组进程的内存使用
  • 统计内存使用
  • OOM 控制

14.2 mem_cgroup 结构

位置: include/linux/memcontrol.h

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
struct mem_cgroup {
// cgroup 基础
struct cgroup_subsys_state css;

// 内存限制
unsigned long memory;
unsigned long swap;
unsigned long memsw;

// 使用统计
unsigned long memory_usage;
unsigned long swap_usage;
unsigned long memsw_usage;

// LRU 链表
struct list_head lru;

// 事件
struct mem_cgroup_events {
unsigned long low;
unsigned long high;
unsigned long max;
unsigned long oom;
} events;

// 更多字段...
};

14.3 memcg API

创建/删除:

1
2
3
4
5
6
7
// 创建 memcg
struct mem_cgroup *mem_cgroup_alloc(void);
void mem_cgroup_free(struct mem_cgroup *memcg);

// 在线/离线
int mem_cgroup_online(struct mem_cgroup *memcg);
void mem_cgroup_offline(struct mem_cgroup *memcg);

内存分配:

1
2
3
4
5
6
// 分配时检查 memcg 限制
bool mem_cgroup_charge(struct page *page, struct mm_struct *mm,
gfp_t gfp_mask);

// 取消收费
void mem_cgroup_uncharge(struct page *page);

14.4 memcg 统计

统计接口:

1
2
3
4
5
6
7
8
// 获取内存使用
unsigned long mem_cgroup_usage(struct mem_cgroup *memcg, bool swap);

// 检查是否超过限制
bool mem_cgroup_over_limit(struct mem_cgroup *memcg);

// OOM 处理
void mem_cgroup_oom(struct mem_cgroup *memcg, gfp_t gfp_mask);

15. 内存统计与调试

15.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
44
45
# /proc/meminfo
MemTotal: 16384000 kB
MemFree: 102400 kB
MemAvailable: 8192000 kB
Buffers: 512000 kB
Cached: 6144000 kB
SwapCached: 0 kB
Active: 5120000 kB
Inactive: 7168000 kB
Active(anon): 4096000 kB
Inactive(anon): 1024000 kB
Active(file): 1024000 kB
Inactive(file): 6144000 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 8388608 kB
SwapFree: 8388608 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 4096000 kB
Mapped: 512000 kB
Shmem: 256000 kB
Slab: 768000 kB
SReclaimable: 512000 kB
SUnreclaim: 256000 kB
KernelStack: 25600 kB
PageTables: 51200 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 16588800 kB
Committed_AS: 8192000 kB
VmallocTotal: 1073741824 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 1024 kB
HardwareCorrupted: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 102400 kB
DirectMap2M: 5120000 kB
DirectMap1G: 10240000 kB

每个进程统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# /proc/[pid]/status
VmPeak: 10240 kB
VmSize: 8192 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 4096 kB
VmRSS: 2048 kB
RssAnon: 1024 kB
RssFile: 1024 kB
RssShmem: 0 kB
VmData: 2048 kB
VmStk: 128 kB
VmExe: 64 kB
VmLib: 512 kB
VmPTE: 16 kB
VmSwap: 0 kB
HugetlbPages: 0 kB

15.2 调试工具

slabtop:

1
2
3
4
5
# 显示 slab 缓存信息
slabtop

# Active / Total Objects (% used)
# 12345 / 67890 (18%)

vmstat:

1
2
3
4
5
6
# 显示虚拟内存统计
vmstat 1

procs -----------memory---------- ---swap-- -----io---- -system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 102400 512000 6144000 0 0 10 5 100 50 5 1 93 1 0

pmap:

1
2
3
4
5
6
7
8
# 显示进程内存映射
pmap 1234

1234: command
0000000000400000 128K r-x-- /path/to/executable
0000000000600000 64K rw--- /path/to/executable
0000000001a00000 8192K rw--- [ anon ]
00007fff7fff0000 16K rw--- [ stack ]

15.3 调试选项

内核配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 内存调试
CONFIG_DEBUG_PAGEALLOC # 调试页面分配
CONFIG_DEBUG_PAGEALLOC_ENABLE_DEFAULT
CONFIG_PAGE_POISONING # 页面投毒
CONFIG_PAGE_POISONING_NO_SANITY
CONFIG_PAGE_POISONING_ZERO # 投毒后清零
CONFIG_DEBUG_PAGE_REF # 页面引用计数调试
CONFIG_DEBUG_KMEMLEAK # 内存泄漏检测
CONFIG_DEBUG_VM # VM 调试
CONFIG_DEBUG_VM_PGFLAGS # 页面标志调试
CONFIG_DEBUG_VIRTUAL # 虚拟地址调试
CONFIG_DEBUG_MEMORY_INIT # 内存初始化调试

# Slab 调试
CONFIG_SLUB_DEBUG # SLUB 调试
CONFIG_SLUB_DEBUG_ON # 启用 SLUB 调试
CONFIG_SLAB_DEBUG # Slab 调试
CONFIG_DEBUG_KOBJECT_RELEASE # 对象释放调试

# 其他
CONFIG_MEMCG_KMEM # memcg 内核内存
CONFIG_MEMCG_SWAP # memcg 交换
CONFIG_TRANSPARENT_HUGEPAGE_DEBUG # THP 调试

附录 A: 系统调用参考

mmap 相关

系统调用 描述 头文件
mmap() 创建内存映射 sys/mman.h
mmap2() 创建内存映射 (32位) sys/mman.h
munmap() 删除内存映射 sys/mman.h
mprotect() 修改内存保护 sys/mman.h
msync() 同步内存映射到文件 sys/mman.h
mremap() 重新映射内存 sys/mman.h
mlock() 锁定内存 sys/mman.h
munlock() 解锁内存 sys/mman.h
mlockall() 锁定所有内存 sys/mman.h
munlockall() 解锁所有内存 sys/mman.h
mincore() 检查页面是否在内存中 sys/mman.h
madvise() 给出内存使用建议 sys/mman.h
remap_file_pages() 重新映射文件页面 sys/mman.h

brk 相关

系统调用 描述 头文件
brk() 改变数据段大小 unistd.h
sbrk() 改变数据段位置 unistd.h

其他内存相关

系统调用 描述 头文件
mbind() 设置内存策略 numaif.h
get_mempolicy() 获取内存策略 numaif.h
set_mempolicy() 设置内存策略 numaif.h
move_pages() 移动页面 numaif.h
migrate_pages() 迁移页面 numaif.h

附录 B: /proc 和 /sys 接口

/proc 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 全局内存信息
/proc/meminfo # 内存统计
/proc/vmstat # 虚拟内存统计
/proc/buddyinfo # buddy 系统信息
/proc/pagetypeinfo # 页面类型信息
/proc/zoneinfo # zone 信息
/proc/slabinfo # slab 信息

# 每个进程
/proc/[pid]/maps # 内存映射
/proc/[pid]/smaps # 详细内存映射
/proc/[pid]/status # 进程状态 (含内存)
/proc/[pid]/statm # 内存统计
/proc/[pid]/pagemap # 页表映射

/sys 接口

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
# 节点信息
/sys/devices/system/node/ # NUMA 节点
/sys/devices/system/node/node*/meminfo
/sys/devices/system/node/node*/numastat

# 内存块
/sys/devices/system/memory/ # 内存块
/sys/devices/system/memory/block_size_bytes
/sys/devices/system/memory/probe
/sys/devices/system/memory/memory*/online

# Zone 信息
/sys/devices/system/node/zone*/ # zone 信息

# 内存调优
/proc/sys/vm/dirty_background_ratio
/proc/sys/vm/dirty_ratio
/proc/sys/vm/dirty_expire_centisecs
/proc/sys/vm/dirty_writeback_centisecs
/proc/sys/vm/swappiness
/proc/sys/vm/vfs_cache_pressure
/proc/sys/vm/overcommit_memory
/proc/sys/vm/overcommit_ratio
/proc/sys/vm/min_free_kbytes
/proc/sys/vm/max_map_count

附录 C: 相关配置选项

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
# 内存模型
CONFIG_FLATMEM # 平坦内存
CONFIG_DISCONTIGMEM # 不连续内存
CONFIG_SPARSEMEM # 稀疏内存
CONFIG_SPARSEMEM_VMEMMAP # 稀疏内存 vmemmap

# 页面大小
CONFIG_PAGE_SIZE_4KB
CONFIG_PAGE_SIZE_16KB
CONFIG_PAGE_SIZE_64KB

# 巨页
CONFIG_HUGETLBFS # 巨页文件系统
CONFIG_HUGETLB_PAGE # 巨页支持
CONFIG_TRANSPARENT_HUGEPAGE # 透明巨页

# 内存管理
CONFIG_ZONE_DMA # DMA 区域
CONFIG_ZONE_DMA32 # DMA32 区域
CONFIG_ARCH_MEMORY_PROBE # 内存探测

# 分配器
CONFIG_SLUB # SLUB 分配器 (默认)
CONFIG_SLAB # SLAB 分配器
CONFIG_SLOB # SLOB 分配器

# 页面回收
CONFIG_SWAP # 交换支持
CONFIG_SWAPFILE # 交换文件支持
CONFIG_SWAPPY # 交换策略

# 内存压缩
CONFIG_COMPACTION # 内存压缩
CONFIG_MIGRATION # 页面迁移
CONFIG_COMPACTION_USE_CMA_

# KSM
CONFIG_KSM # 内核同页合并

# 内存 cgroup
CONFIG_MEMCG # 内存控制组
CONFIG_MEMCG_SWAP # memcg 交换
CONFIG_MEMCG_KMEM # memcg 内核内存

# 调试
CONFIG_DEBUG_PAGEALLOC # 页面分配调试
CONFIG_DEBUG_KMEMLEAK # 内存泄漏检测
CONFIG_DEBUG_VM # VM 调试
CONFIG_DEBUG_SLAB # Slab 调试

# 其他
CONFIG_MMU # MMU 支持
CONFIG_PGTABLE_LEVELS # 页表级别
CONFIG_ARCH_SPARSEMEM_ENABLE
  • Title: Linux内核分析之内存管理-00
  • Author: 韩乔落
  • Created at : 2026-01-20 21:37:20
  • Updated at : 2026-02-24 14:05:34
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-00/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Linux内核分析之内存管理-00