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

韩乔落

第13章:内存压缩与迁移

基于 Linux 6.12.38 源码


13.1 内存压缩概述

13.1.1 为什么需要内存压缩

内存压缩 (Compaction) 用于解决内存碎片问题:

碎片类型:

  • 外部碎片: 可用页面分散,无法满足大块连续分配
  • 内部碎片: 页面部分使用,浪费空间

压缩目的:

  • 减少内存碎片
  • 创建连续的大块内存
  • 满足高阶分配请求
  • 支持 THP (透明巨页)

13.1.2 压缩原理

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 │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
使用 使用 使用 使用 使用 使用 使用 空闲 空闲 空闲 空闲 空闲 空闲 空闲 空闲

13.2 内存压缩实现

13.2.1 compact_zone

位置: mm/compaction.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.2 compact_control 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct compact_control {
unsigned long nr_freepages; /* 空闲页面数 */
unsigned long nr_migratepages; /* 迁移页面数 */
unsigned long total_migratepages; /* 总迁移页面数 */
unsigned long total_freepages; /* 总空闲页面数 */

struct zone *zone; /* 压缩的 zone */
int order; /* 目标 order */
gfp_t gfp_mask; /* GFP 标志 */
enum migrate_mode mode; /* 迁移模式 */
unsigned int alloc_flags; /* 分配标志 */
int classzone_idx; /* 类 zone 索引 */

/* 扫描器位置 */
unsigned long free_pfn; /* 空闲扫描器位置 */
unsigned long migrate_pfn; /* 迁移扫描器位置 */

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

13.2.3 压缩流程

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
compact_zone_order(zone, order=2)


┌──────────────────────────────────────┐
│ 1. 初始化 compact_control │
│ - 设置 order, gfp_mask │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 检查是否需要压缩 │
│ - compaction_suitable │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 空闲扫描 (isolate_freepages) │
│ - 扫描可移动页面 │
│ - 隔离到迁移列表 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 迁移扫描 (migrate_pages) │
│ - 扫描空闲页面 │
│ - 迁移可移动页面到空闲位置 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 检查是否满足要求 │
│ - nr_freepages >= 目标 │
│ - 如果满足,返回成功 │
│ - 如果不满足,继续扫描 │
└──────────────────────────────────────┘

13.3 页面迁移

13.3.1 迁移类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 迁移模式 */
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 错位 */
MR_CONTIG_RANGE, /* 连续内存分配 */
MR_CMA, /* CMA 分配 */
MR_LONGTERM_PIN, /* 长期固定 */
MR_DEMOTION, /* 降级 */
MR_TYPES
};

13.3.2 migrate_pages

位置: mm/migrate.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
/* 迁移页面 */
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)
{
struct page *page;
struct page *newpage;
int rc = 0;

/* 遍历迁移列表 */
list_for_each_entry_safe(page, from, lru) {
/* 获取新页面 */
newpage = get_new_page(page, private);
if (!newpage) {
rc = -ENOMEM;
break;
}

/* 迁移页面 */
rc = unmap_and_move(new_page, page, mode);

if (rc == -EAGAIN) {
/* 重试 */
putback_lru_page(page);
continue;
}

if (rc) {
/* 失败 */
put_newpage(page, private);
break;
}

/* 成功 */
put_newpage(page, private);
}

return rc;
}

13.3.3 迁移流程

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
migrate_pages(from_list, get_new_page, ...)


┌──────────────────────────────────────┐
│ 1. 遍历迁移列表 │
│ - list_for_each_entry_safe │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 获取新页面 │
│ - get_new_page │
│ - alloc_page │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 取消旧页面映射 │
│ - try_to_unmap │
│ - 更新页表 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 复制页面内容 │
│ - copy_highpage │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 恢复新页面映射 │
│ - remove_migration_ptes │
│ - 建立新映射 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 6. 释放旧页面 │
│ - put_page │
└──────────────────────────────────────┘

13.4 kcompactd

13.4.1 kcompactd 内核线程

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
24
25
26
27
static int kcompactd(void *p)
{
struct pglist_data *pgdat = p;
struct compact_control cc;
int default_order = -1;

set_freezable();

while (!kthread_should_stop()) {
/* 等待压缩请求 */
wait_event_freezable(pgdat->kcompactd_wait,
kcompactd_work_requested(pgdat));

/* 执行压缩 */
for_each_zone_zonelist(zone, zonelist, zone_idx) {
struct zone *zone = pgdat->node_zones + zone_idx;

if (!compaction_suitable(zone, default_order))
continue;

/* 压缩 zone */
compact_zone(zone, &cc);
}
}

return 0;
}

13.4.2 触发压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 请求压缩 */
void wakeup_kcompactd(pg_data_t *pgdat, int order,
enum zone_type highest_zoneidx)
{
if (!pgdat->kcompactd)
return;

if (order == -1)
order = pgdat->kcompactd_max_order;

pgdat->kcompactd_max_order = order;
pgdat->kcompactd_highest_zoneidx = highest_zoneidx;

wake_up_interruptible(&pgdat->kcompactd_wait);
}

13.5 内存策略

13.5.1 mempolicy

NUMA 系统中,内存策略控制进程的内存分配位置。

13.5.2 mempolicy 结构

位置: include/linux/mempolicy.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct mempolicy {
atomic_t refcnt; /* 引用计数 */

unsigned short mode; /* 策略模式 */
unsigned short flags; /* 标志 */
nodemask_t nodes; /* 节点掩码 */

union {
nodemask_t cpuset_mems; /* cpuset 节点 */
nodemask_t vmas; /* VMA 策略 */
} v;

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

13.5.3 内存策略模式

1
2
3
4
5
6
7
/* 内存策略模式 */
#define MPOL_DEFAULT 0 /* 默认策略 */
#define MPOL_PREFERRED 1 /* 首选节点 */
#define MPOL_BIND 2 /* 绑定节点 */
#define MPOL_INTERLEAVE 3 /* 交错分配 */
#define MPOL_WEIGHTED_INTERLEAVE 4 /* 加权交错 */
#define MPOL_HOMOGENEOUS 5 /* 同构分配 */

13.5.4 内存策略 API

1
2
3
4
5
6
7
8
9
10
11
/* 设置内存策略 */
long do_set_mempolicy(unsigned short mode, unsigned short flags,
nodemask_t *nodes, unsigned long flags);

/* 获取内存策略 */
long do_get_mempolicy(unsigned short mode, unsigned short flags,
unsigned long *addr, unsigned long flags);

/* 分配时应用策略 */
struct page *alloc_pages_mpol(gfp_t gfp, unsigned int order,
struct mempolicy *pol);

13.6 CMA (Contiguous Memory Allocator)

13.6.1 CMA 概述

CMA 用于分配大块连续内存,主要用于:

  • 驱动程序
  • DMA 缓冲
  • 巨页分配

13.6.2 CMA 分配

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
/* 分配连续内存范围 */
int alloc_contig_range_noprof(unsigned long start, unsigned long end,
unsigned migratetype, gfp_t gfp_mask)
{
unsigned long outer_start, outer_end;
int ret;

/* 隔离页面 */
ret = isolate_freepages_range(start, end, migratetype);
if (ret)
return ret;

/* 迁移页面 */
ret = migrate_pages(&cc, GFP_KERNEL);

if (ret) {
/* 取消隔离 */
undo_isolate_page_range(start, end);
return ret;
}

return 0;
}

/* 分配连续页面 */
struct page *alloc_contig_pages_noprof(unsigned long nr_pages, gfp_t gfp_mask,
int nid, nodemask_t *nodemask)
{
/* 查找合适的范围 */
/* 分配 */
/* 返回页面 */
}

13.7 页面迁移状态

13.7.1 迁移页面状态

1
2
3
4
5
6
/* 迁移页状态 */
enum migration_entry_status {
MIGRATE_SUCCESS, /* 迁移成功 */
MIGRATE_AGAIN, /* 重试 */
MIGRATE_FAILURE, /* 失败 */
};

13.7.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
/* 检查页面是否可迁移 */
static inline bool migrate_page_address(struct page *page)
{
/* 检查页面引用 */
if (page_mapcount(page) == 0)
return false;

/* 检查页面标志 */
if (PageCompound(page))
return false;

return true;
}

/* 迁移页检查 */
bool isolate_movable_page(struct page *page)
{
/* 检查是否可移动 */
if (!PageLRU(page))
return false;

if (!PageMovable(page))
return false;

/* 隔离页面 */
return isolate_lru_page(page);
}

13.8 本章小结

本章介绍了 Linux 6.12 的内存压缩与迁移:

  1. 内存压缩: 减少碎片,创建连续内存
  2. 压缩实现: compact_zone_order,compact_control
  3. 页面迁移: 迁移页面到新位置,整理碎片
  4. kcompactd: 内存压缩内核线程
  5. 内存策略: NUMA 策略,控制分配位置
  6. CMA: 连续内存分配器,用于大块内存
  7. 迁移状态: 迁移页面状态检查

下一章将介绍内存控制组。

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