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

韩乔落

第3章:物理内存分配器

基于 Linux 6.12.38 源码


3.1 Buddy System 概述

3.1.1 分配器原理

伙伴系统 (Buddy System) 是 Linux 内核用于管理物理内存的核心算法。

核心思想:

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

MAX_ORDER 定义: include/linux/mmzone.h:29

1
2
3
4
5
6
7
#ifndef CONFIG_ARCH_FORCE_MAX_ORDER
#define MAX_PAGE_ORDER 10
#else
#define MAX_PAGE_ORDER CONFIG_ARCH_FORCE_MAX_ORDER
#endif
#define MAX_ORDER_NR_PAGES (1 << MAX_PAGE_ORDER)
#define NR_PAGE_ORDERS (MAX_PAGE_ORDER + 1)

PAGE_ALLOC_COSTLY_ORDER: 大于此 order 的分配被认为是”昂贵的”

1
#define PAGE_ALLOC_COSTLY_ORDER 3

3.1.2 空闲区域数组

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

1
2
3
4
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};

3.1.3 Buddy System 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 (合并块)

每个 order 的 free_area 有 MIGRATE_TYPES 个链表:
free_area[order].free_list[MIGRATE_UNMOVABLE]
free_area[order].free_list[MIGRATE_MOVABLE]
free_area[order].free_list[MIGRATE_RECLAIMABLE]
...

3.2 迁移类型 (Migrate Type)

3.2.1 迁移类型定义

为了支持内存迁移和反碎片,Linux 将页面分为不同的迁移类型。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum migratetype {
MIGRATE_UNMOVABLE, /* 不可移动页面 (内核页面、DMA 页面) */
MIGRATE_MOVABLE, /* 可移动页面 (页面缓存、匿名页面) */
MIGRATE_RECLAIMABLE, /* 可回收页面 (文件映射页面) */
MIGRATE_PCPTYPES, /* pcp 列表上的类型数量 */
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
MIGRATE_CMA, /* CMA (Contiguous Memory Allocator) */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* 隔离页面,不能从这里分配 */
#endif
MIGRATE_TYPES
};

3.2.2 迁移类型说明

类型 描述 示例
MIGRATE_UNMOVABLE 不可移动 内核栈、DMA 页面
MIGRATE_MOVABLE 可移动 页面缓存、匿名页面
MIGRATE_RECLAIMABLE 可回收 文件映射页面
MIGRATE_CMA CMA 区域 连续内存分配器
MIGRATE_ISOLATE 隔离 内存热插拔

3.2.3 迁移类型与 GFP 标志的对应

位置: include/linux/gfp.h:19

1
2
3
4
5
6
7
8
9
10
static inline int gfp_migratetype(const gfp_t gfp_flags)
{
VM_WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);

if (unlikely(page_group_by_mobility_disabled))
return MIGRATE_UNMOVABLE;

/* 基于可移动性分组 */
return (__force unsigned long)(gfp_flags & GFP_MOVABLE_MASK) >> GFP_MOVABLE_SHIFT;
}

3.3 分配页面

3.3.1 核心分配函数

位置: mm/page_alloc.cinclude/linux/gfp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 分配 2^order 个连续页面 */
struct page *__alloc_pages_noprof(gfp_t gfp, unsigned int order,
int preferred_nid, nodemask_t *nodemask);

/* 从指定节点分配 */
static inline struct page *__alloc_pages_node_noprof(int nid, gfp_t gfp_mask,
unsigned int order)
{
VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
warn_if_node_offline(nid, gfp_mask);

return __alloc_pages_noprof(gfp_mask, order, nid, NULL);
}

/* 分配单页 */
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

/* 从 VMA 分配 */
static inline struct page *alloc_page_vma_noprof(gfp_t gfp,
struct vm_area_struct *vma,
unsigned long addr);

3.3.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
alloc_pages(GFP_KERNEL, order=2)


┌──────────────────────────────────────┐
│ 1. 快速路径 (fast path) │
│ - 检查 PCP 缓存 │
│ - 检查 free_area[order] │
│ - 如果有足够空闲页,直接返回 │
└──────────────────────────────────────┘

没有足够空间

┌──────────────────────────────────────┐
│ 2. 慢速路径唤醒 │
│ - 唤醒 kswapd 异步回收 │
│ - 唤醒 kcompactd 进行内存压缩 │
└──────────────────────────────────────┘


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


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


┌──────────────────────────────────────┐
│ 5. 内存压缩 (compaction) │
│ - 整理碎片,形成连续内存 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 6. OOM Killer (最后手段) │
│ - 杀死进程释放内存 │
└──────────────────────────────────────┘

3.3.3 分配路径详解

快速路径 (Fast Path):

  • 尝试从 PCP (Per-CPU Pageset) 获取页面
  • 检查 zone 的水位
  • 如果满足条件,直接返回

慢速路径 (Slow Path):

  • 唤醒 kswapd 进行异步页面回收
  • 尝试从更大的 order 分割页面
  • 执行直接回收 (direct reclaim)
  • 执行内存压缩 (compaction)
  • 最后才触发 OOM Killer

3.4 释放页面

3.4.1 释放函数

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

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

/* 释放 folio */
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

3.4.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
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.4.3 伙伴合并算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 计算伙伴页面的 PFN */
static inline unsigned long
__find_buddy_pfn(unsigned long pfn, unsigned int order)
{
return pfn ^ (1 << order);
}

/* 检查是否可以合并 */
static inline bool page_is_buddy(struct page *page, struct page *buddy,
unsigned int order)
{
if (!page_is_buddy(buddy, order))
return false;

if (page_order(buddy) != order)
return false;

return true;
}

3.5 GFP 标志

3.5.1 GFP 标志定义

GFP (Get Free Pages) 标志控制内存分配的行为。

位置: include/linux/gfp_types.h

区域修饰符 (Zone Modifiers):

1
2
3
4
#define ___GFP_DMA        0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u

行为修饰符 (Action Modifiers):

1
2
3
4
5
6
7
8
9
#define ___GFP_RECLAIM        0x10u   /* 可以回收 */
#define ___GFP_HIGH 0x20u /* 高优先级 (__GFP_ATOMIC) */
#define ___GFP_IO 0x40u /* 可以执行 I/O */
#define ___GFP_FS 0x80u /* 可以调用文件系统 */
#define ___GFP_NOWARN 0x200u /* 不打印警告 */
#define ___GFP_RETRY_MAYFAIL 0x400u /* 重试但可能失败 */
#define ___GFP_NORETRY 0x1000u /* 不重试 */
#define ___GFP_MEMALLOC 0x2000u /* 内存分配上下文 */
#define ___GFP_HARDWALL 0x80000u /* 必须严格遵守区域限制 */

3.5.2 常用 GFP 组合

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
/*
* 可以睡眠和回收的物理内存分配。
* 这是内核中最常用的分配标志。
*/
#define GFP_KERNEL \
(GFP_NOWAIT | __GFP_KSWAPD_RECLAIM)

/*
* 原子上下文分配 (不睡眠)。
* 用于中断处理、持有自旋锁等场景。
*/
#define GFP_ATOMIC \
(___GFP_HIGH | ___GFP_KSWAPD_RECLAIM)

/*
* 用户空间分配。
*/
#define GFP_USER \
(GFP_KERNEL | __GFP_HARDWALL)

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

#define GFP_HIGHUSER_MOVABLE \
(GFP_USER | __GFP_MOVABLE | __GFP_SKIP_KASAN_POISON)

/*
* DMA 分配。
*/
#define GFP_DMA __GFP_DMA

3.5.3 GFP 标志使用指南

场景 推荐标志 说明
进程上下文,可睡眠 GFP_KERNEL 最常用,可回收和睡眠
中断上下文,持锁 GFP_ATOMIC 不睡眠,高优先级
用户空间内存 GFP_USER 用户空间分配
DMA 内存 GFP_DMA DMA 可访问内存
高端内存 GFP_HIGHUSER 32位系统

3.6 Per-CPU 页面缓存 (PCP)

3.6.1 PCP 结构

为了减少自旋锁竞争和提高分配性能,Linux 实现了 Per-CPU 页面缓存。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct per_cpu_pages {
spinlock_t lock; /* 保护 lists 字段 */
int count; /* 列表中的页面数量 */
int high; /* 高水位,需要清空 */
int high_min; /* 最小高水位 */
int high_max; /* 最大高水位 */
int batch; /* buddy add/remove 的块大小 */
u8 flags; /* 由 pcp->lock 保护 */
u8 alloc_factor; /* 分配期间的批量缩放因子 */
#ifdef CONFIG_NUMA
u8 expire; /* 当为 0 时,远程 pageset 被排空 */
#endif
short free_count; /* 连续空闲计数 */

/* 页面列表,每个迁移类型一个 */
struct list_head lists[NR_PCP_LISTS];
} ____cacheline_aligned_in_smp;

3.6.2 PCP 列表数量

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

1
2
3
#define NR_LOWORDER_PCP_LISTS (MIGRATE_PCPTYPES * (PAGE_ALLOC_COSTLY_ORDER + 1))
#define NR_PCP_THP 2
#define NR_PCP_LISTS (NR_LOWORDER_PCP_LISTS + NR_PCP_THP)

3.6.3 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 获取 │
└──────────────────────────────────────┘

3.6.4 PCP 相关 API

1
2
3
4
5
6
7
8
9
10
11
/* 排空 PCP 缓存 */
void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp);

/* 排空所有页面 */
void drain_all_pages(struct zone *zone);

/* 排空本地页面 */
void drain_local_pages(struct zone *zone);

/* 衰减 PCP 高水位 */
int decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp);

3.7 内存分配优化

3.7.1 批量分配

1
2
3
4
5
6
7
8
9
10
11
/* 批量分配 API */
unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid,
nodemask_t *nodemask, int nr_pages,
struct list_head *page_list,
struct page **page_array);

#define alloc_pages_bulk_list(_gfp, _nr_pages, _list) \
__alloc_pages_bulk(_gfp, numa_mem_id(), NULL, _nr_pages, _list, NULL)

#define alloc_pages_bulk_array(_gfp, _nr_pages, _page_array) \
__alloc_pages_bulk(_gfp, numa_mem_id(), NULL, _nr_pages, NULL, _page_array)

3.7.2 精确分配

1
2
3
4
5
6
7
8
/* 分配指定大小的连续内存 */
void *alloc_pages_exact_noprof(size_t size, gfp_t gfp_mask);

/* 释放精确分配的内存 */
void free_pages_exact(void *virt, size_t size);

/* 从指定节点精确分配 */
__meminit void *alloc_pages_exact_nid_noprof(int nid, size_t size, gfp_t gfp_mask);

3.7.3 连续内存分配

1
2
3
4
5
6
7
8
9
10
/* 分配连续内存范围 (用于 CMA) */
int alloc_contig_range_noprof(unsigned long start, unsigned long end,
unsigned migratetype, gfp_t gfp_mask);

/* 分配连续页面 */
struct page *alloc_contig_pages_noprof(unsigned long nr_pages, gfp_t gfp_mask,
int nid, nodemask_t *nodemask);

/* 释放连续内存范围 */
void free_contig_range(unsigned long pfn, unsigned long nr_pages);

3.8 本章小结

本章介绍了 Linux 6.12 的物理内存分配器:

  1. Buddy System: 2^n 次幂组织,伙伴合并算法
  2. 迁移类型: UNMOVABLE、MOVABLE、RECLAIMABLE、CMA、ISOLATE
  3. 分配流程: 快速路径 → 慢速路径 → 直接回收 → 压缩 → OOM
  4. 释放流程: 释放页面 → 检查伙伴 → 递归合并
  5. GFP 标志: 控制分配行为的标志位
  6. PCP 缓存: Per-CPU 页面缓存,提高分配性能
  7. 优化技术: 批量分配、精确分配、连续内存分配

下一章将介绍虚拟地址空间。

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