第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; int order; gfp_t gfp_mask; enum migrate_mode mode; unsigned int alloc_flags; int classzone_idx;
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, MR_CONTIG_RANGE, MR_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;
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; nodemask_t vmas; } 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 用于分配大块连续内存,主要用于:
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 的内存压缩与迁移:
- 内存压缩: 减少碎片,创建连续内存
- 压缩实现: compact_zone_order,compact_control
- 页面迁移: 迁移页面到新位置,整理碎片
- kcompactd: 内存压缩内核线程
- 内存策略: NUMA 策略,控制分配位置
- CMA: 连续内存分配器,用于大块内存
- 迁移状态: 迁移页面状态检查
下一章将介绍内存控制组。