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

韩乔落

第7章:页面分配

基于 Linux 6.12.38 源码


7.1 页面分配概述

7.1.1 分配层次

Linux 内核提供了多种内存分配方式,适用于不同的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│ 内存分配层次 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 高层分配 (kmalloc, vmalloc) │
│ ↓ │
│ 页面分配 (alloc_pages, __get_free_pages) │
│ ↓ │
│ Buddy System (伙伴系统) │
│ ↓ │
│ 物理页面 (struct page) │
│ │
└─────────────────────────────────────────────────────────────┘

7.1.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
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
┌─────────────────────────────────────────────────────────────────────┐
│ 用户空间内存分配完整调用链 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户代码 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ptr = malloc(1024) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ brk() / mmap(MAP_ANONYMOUS) ────────┐ │ │
│ └────────────────────────────────────────┼────────────────────┘ │
│ 系统调用 │ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ sys_brk / sys_mmap_pgoff (ch11, ch12) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ do_mmap() → 创建 VMA (vm_area_struct) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ 首次访问触发缺页异常 (Page Fault) │ │ │
│ │ │ │ │ │
│ │ │ do_page_fault() → handle_mm_fault() │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ do_anonymous_page() (ch09) │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ alloc_pages(GFP_KERNEL, order) ──┐ │ │ │
│ │ │ │ │ │ │
│ │ └────────────────────────────────────┼────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────────────────┼────────────────────┐ │ │
│ │ │ __alloc_pages_noprof() │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ├──► 快速路径: PCP缓存 ────┤ │ │ │
│ │ │ │ (per_cpu_pages) │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ├──► 慢速路径: │ │ │ │
│ │ │ │ - 唤醒 kswapd │ │ │ │
│ │ │ │ - Buddy分配 │ │ │ │
│ │ │ │ - 直接回收 (ch10) │ │ │ │
│ │ │ │ - 内存压缩 (ch13) │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ▼ ▼ │ │ │
│ │ │ struct page *page (物理页面) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 设置页表: set_pte_at() ──▶ 建立虚拟→物理映射 (ch05) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Slab小对象分配 (kmalloc) │ │
│ │ │ │
│ │ kmalloc(128) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ kmem_cache_alloc(kmalloc_caches[index]) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ __do_kmalloc() → 如果size > PAGE_SIZE, 调用 alloc_pages() │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 图例说明: │
│ ───────── │
│ ───► : 直接调用流 │
│ ──┐ : 条件分支 │
│ │ │ │
│ └─┘ │
│ (chXX) : 对应章节 │
│ │
└─────────────────────────────────────────────────────────────────────┘

7.1.3 分配 API 分类

类型 API 单位 用途
物理页面 alloc_pages() 页 (4KB) 物理内存分配
虚拟地址 __get_free_pages() 页 (4KB) 直接映射区虚拟地址
字节 kmalloc() 字节 小对象分配
非连续 vmalloc() 字节 非连续虚拟内存
精确 alloc_pages_exact() 字节 精确大小分配

7.2 alloc_pages 函数族

7.2.1 核心分配函数

位置: include/linux/gfp.h

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

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

/* 分配多个页面 */
struct page *alloc_pages_noprof(gfp_t gfp, unsigned int order);

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

/* 从指定节点分配 (带 NUMA_NO_NODE 检查) */
static inline struct page *alloc_pages_node_noprof(int nid, gfp_t gfp_mask,
unsigned int order)
{
if (nid == NUMA_NO_NODE)
nid = numa_mem_id();

return __alloc_pages_node_noprof(nid, gfp_mask, order);
}

7.2.2 从 VMA 分配

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 从 VMA 分配页面 (考虑 VMA 策略) */
static inline struct page *alloc_page_vma_noprof(gfp_t gfp,
struct vm_area_struct *vma,
unsigned long addr)
{
struct folio *folio = vma_alloc_folio_noprof(gfp, 0, vma, addr, false);
return &folio->page;
}

/* 分配 folio */
struct folio *vma_alloc_folio_noprof(gfp_t gfp, int order,
struct vm_area_struct *vma,
unsigned long addr, bool hugepage);

7.2.3 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 分配单页 */
struct page *page = alloc_page(GFP_KERNEL);
if (!page)
return -ENOMEM;

/* 分配 8 页 (order=3, 32KB) */
struct page *pages = alloc_pages(GFP_KERNEL, 3);
if (!pages)
return -ENOMEM;

/* 使用页面 */
void *addr = page_address(page);

/* 释放页面 */
__free_pages(page, 0);
__free_pages(pages, 3);

7.3 __get_free_pages 函数族

7.3.1 函数定义

1
2
3
4
5
6
7
8
9
10
11
12
/* 分配页面并返回内核虚拟地址 */
unsigned long get_free_pages_noprof(gfp_t gfp_mask, unsigned int order);

/* 分配并清零 */
unsigned long get_zeroed_page_noprof(gfp_t gfp_mask);

/* 快捷宏 */
#define __get_free_page(gfp_mask) \
__get_free_pages((gfp_mask), 0)

#define __get_dma_pages(gfp_mask, order) \
__get_free_pages((gfp_mask) | GFP_DMA, (order))

7.3.2 使用示例

1
2
3
4
5
6
7
8
9
10
/* 分配一个页面,返回虚拟地址 */
unsigned long addr = __get_free_page(GFP_KERNEL);
if (!addr)
return -ENOMEM;

/* 使用内存 */
memset((void *)addr, 0, PAGE_SIZE);

/* 释放 */
free_page(addr);

7.3.3 与 alloc_pages 的区别

特性 alloc_pages() __get_free_pages()
返回类型 struct page * unsigned long (虚拟地址)
地址范围 任意物理内存 直接映射区
高端内存 支持 仅当启用 CONFIG_HIGHMEM
典型用途 需要操作 struct page 需要直接访问内存

7.4 释放页面

7.4.1 释放函数

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

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

/* 释放单页快捷宏 */
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

7.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
30
31
32
33
34
35
__free_pages(page, order=2)


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


┌──────────────────────────┐
│ 2. 检查是否为复合页 │
│ - 如果是 folio,特殊处理 │
└──────────────────────────┘


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


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


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

7.4.3 释放注意事项

1
2
3
4
5
6
7
8
9
10
11
12
/* 不要释放 NULL 页面 */
if (page)
__free_pages(page, order);

/* 不要释放引用计数不为 0 的页面 */
if (page_ref_count(page) == 0)
__free_pages(page, order);

/* 正确的释放模式 */
get_page(page); /* 增加引用 */
/* ... 使用页面 ... */
put_page(page); /* 减少引用,如果为 0 则释放 */

7.5 页面引用计数

7.5.1 引用计数操作

位置: include/linux/page_ref.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
/* 增加引用计数 */
static inline void get_page(struct page *page);
static inline struct page *grab_page(struct page *page);

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

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

/* 检查引用计数 */
static inline int page_ref_count(struct page *page);

/* 设置引用计数 */
static inline void set_page_count(struct page *page, int v);

/* 原子操作 */
static inline void page_ref_inc(struct page *page);
static inline void page_ref_dec(struct page *page);
static inline void page_ref_add(struct page *page, int nr);
static inline void page_ref_sub(struct page *page, int nr);

/* 条件操作 */
bool page_ref_add_unless(struct page *page, int nr, int unless);
bool page_ref_sub_and_test(struct page *page, int nr);
bool page_ref_inc_and_test(struct page *page);
bool page_ref_dec_and_test(struct page *page);
bool page_ref_freeze(struct page *page, int count);
void page_ref_unfreeze(struct page *page, int count);

7.5.2 引用计数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 获取页面引用 */
get_page(page);

/* 使用页面 */
/* ... */

/* 释放引用 */
put_page(page);

/* 条件释放 */
if (page_ref_sub_and_test(page, 1)) {
/* 引用计数为 0,释放页面 */
__free_pages(page, 0);
}

7.6 精确分配

7.6.1 alloc_pages_exact

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);

7.6.2 使用场景

1
2
3
4
5
6
7
8
9
10
/* 分配 10KB 的连续内存 (非 2^n 次幂) */
void *mem = alloc_pages_exact(10240, GFP_KERNEL);
if (!mem)
return -ENOMEM;

/* 使用内存 */
memcpy(mem, data, 10240);

/* 释放 */
free_pages_exact(mem, 10240);

7.7 批量分配

7.7.1 批量分配 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 批量分配到列表 */
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)

/* 从指定节点批量分配 */
static inline unsigned long
alloc_pages_bulk_array_node_noprof(gfp_t gfp, int nid, unsigned long nr_pages,
struct page **page_array)
{
if (nid == NUMA_NO_NODE)
nid = numa_mem_id();

return alloc_pages_bulk_noprof(gfp, nid, NULL, nr_pages, NULL, page_array);
}

7.7.2 批量分配示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 批量分配 128 个页面到数组 */
struct page *pages[128];
unsigned long nr = alloc_pages_bulk_array(GFP_KERNEL, 128, pages);

/* 使用页面 */
for (unsigned long i = 0; i < nr; i++) {
void *addr = page_address(pages[i]);
/* ... 使用内存 ... */
}

/* 释放页面 */
for (unsigned long i = 0; i < nr; i++) {
__free_page(pages[i]);
}

7.8 页面转换

7.8.1 page 和虚拟地址转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* page → 虚拟地址 */
void *page_address(const struct page *page);
void *lowmem_page_address(const struct page *page);

/* 虚拟地址 → page (仅直接映射区) */
#define virt_to_page(addr) pfn_to_page(PFN_DOWN(__pa(addr)))

/* PFN → page */
#define pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))

/* page → PFN */
#define page_to_pfn(page) ((unsigned long)((page) - mem_map) + \
ARCH_PFN_OFFSET)

/* 虚拟地址 → 物理地址 */
#define __pa(x) ((unsigned long)(x) - PAGE_OFFSET)

/* 物理地址 → 虚拟地址 */
#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))

7.8.2 高端内存映射

1
2
3
4
5
6
7
8
9
10
11
12
/* 临时映射高端内存页面 */
void *kmap_atomic(struct page *page);
void kunmap_atomic(void *kvaddr);

/* 永久映射高端内存页面 */
void *kmap(struct page *page);
void kunmap(struct page *page);

/* 映射页面到用户空间 */
int map_vm_area(struct vm_struct *area, pgprot_t prot,
struct page **pages);
void unmap_vm_area(struct vm_struct *area);

7.9 分配跟踪与调试

7.9.1 分配跟踪

1
2
3
4
5
6
7
/* 分配跟踪钩子 (CONFIG_MEM_ALLOC_PROFILING) */
#define alloc_hooks(__alloc_fn) alloc_hooks_branch(__alloc_fn)

/* 内存分配标签 (CONFIG_MEM_ALLOC_PROFILING) */
struct alloc_tag {
/* 分配标签信息 */
};

7.9.2 调试选项

1
2
3
4
5
6
7
CONFIG_DEBUG_PAGEALLOC         # 调试页面分配
CONFIG_DEBUG_PAGEALLOC_ENABLE_DEFAULT
CONFIG_PAGE_POISONING # 页面投毒
CONFIG_PAGE_POISONING_NO_SANITY
CONFIG_PAGE_POISONING_ZERO # 投毒后清零
CONFIG_PAGE_OWNER # 跟踪页面所有者
CONFIG_DEBUG_KMEMLEAK # 内存泄漏检测

7.9.3 调试函数

1
2
3
4
5
6
7
8
9
10
11
/* 检查页面是否保留 */
static inline int PageReserved(const struct page *page);

/* 检查页面是否 slab */
static inline int PageSlab(const struct page *page);

/* 检查页面是否在伙伴系统 */
static inline int PageBuddy(const struct page *page);

/* 获取页面 order */
static inline unsigned long page_order(const struct page *page);

7.10 本章小结与跨章节关联

7.10.1 本章要点

本章介绍了 Linux 6.12 的页面分配:

  1. 分配层次: kmalloc/vmalloc → alloc_pages → Buddy System → 物理页面
  2. alloc_pages 函数族: 返回 struct page,分配物理页面
  3. __get_free_pages 函数族: 返回虚拟地址,直接映射区
  4. 释放页面: __free_pages,伙伴合并
  5. 引用计数: get_page/put_page,页面生命周期管理
  6. 精确分配: alloc_pages_exact,非 2^n 次幂分配
  7. 批量分配: alloc_pages_bulk,提高效率
  8. 页面转换: page 和虚拟地址互转
  9. 分配跟踪: 调试选项和函数

7.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
29
30
31
32
33
┌────────────────────────────────────────────────────────────┐
│ 本章内容 (页面分配) │
├────────────────────────────────────────────────────────────┤
│ │
│ alloc_pages ──▶ ch02:Node/Zone/Page │
│ (从哪个结构分配) │
│ │
│ alloc_pages ──▶ ch03:Buddy System │
│ (底层分配算法) │
│ │
│ 快速路径 ────▶ ch02:per_cpu_pages │
│ (PCP缓存) (Per-CPU 页面缓存) │
│ │
│ 慢速路径 ────▶ ch10:页面回收 (kswapd/直接回收) │
│ (回收满足分配需求) │
│ │
│ 慢速路径 ────▶ ch13:内存压缩 (kcompactd) │
│ (整理碎片满足高阶分配) │
│ │
│ alloc_pages ──▶ ch05:页表管理 (设置页表映射) │
│ │
│ alloc_pages ──▶ ch06:VMA (匿名映射分配) │
│ │
│ alloc_pages ──▶ ch09:缺页处理 (do_anonymous_page调用) │
│ │
│ alloc_pages ──▶ ch12:页缓存 (文件页分配) │
│ │
│ kmalloc ─────▶ ch08:Slab分配器 │
│ (小对象分配基于页面分配) │
│ │
│ GFP标志 ─────▶ ch14:memcg (内存限制检查) │
│ │
└────────────────────────────────────────────────────────────┘

关键分配路径:

  1. 快速路径: alloc_pages()per_cpu_pages (PCP缓存) → 直接返回

    • 相关章节: ch02 (per_cpu_pages结构)
  2. 慢速路径 - 低水位: alloc_pages()wakeup_kswapd()kswapd回收

    • 相关章节: ch02 (watermark), ch10 (kswapd实现)
  3. 慢速路径 - 直接回收: alloc_pages()__alloc_pages_direct_reclaim()

    • 相关章节: ch10 (直接回收)
  4. 慢速路径 - 压缩: alloc_pages()__alloc_pages_direct_compact()

    • 相关章节: ch13 (内存压缩)
  5. 用户空间分配: malloc()brk()/mmap()缺页异常alloc_pages()

    • 相关章节: ch09 (缺页处理), ch11 (mmap), ch12 (匿名内存)

下一章将介绍 Slab 分配器。

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