第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, MIGRATE_MOVABLE, MIGRATE_RECLAIMABLE, MIGRATE_PCPTYPES, MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES, #ifdef CONFIG_CMA MIGRATE_CMA, #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.c 和 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
| 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)
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);
#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
| 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 #define ___GFP_IO 0x40u #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)
#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; int count; int high; int high_min; int high_max; int batch; u8 flags; u8 alloc_factor; #ifdef CONFIG_NUMA u8 expire; #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
| 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);
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
| 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
| 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 的物理内存分配器:
- Buddy System: 2^n 次幂组织,伙伴合并算法
- 迁移类型: UNMOVABLE、MOVABLE、RECLAIMABLE、CMA、ISOLATE
- 分配流程: 快速路径 → 慢速路径 → 直接回收 → 压缩 → OOM
- 释放流程: 释放页面 → 检查伙伴 → 递归合并
- GFP 标志: 控制分配行为的标志位
- PCP 缓存: Per-CPU 页面缓存,提高分配性能
- 优化技术: 批量分配、精确分配、连续内存分配
下一章将介绍虚拟地址空间。