3. 伙伴系统数据结构:free_area数组、struct page与zone结构体详解
伙伴系统(Buddy System)是Linux内核物理内存管理的核心算法,其高效运作依赖于精心设计的数据结构。本章深入剖析三个关键数据结构:free_area数组、struct page和struct zone,理解它们如何协同完成物理内存的分配与回收。
3.1 核心数据结构总览
伙伴系统的数据结构层次清晰,从全局的struct zone到每个迁移类型的free_area,再到描述每个物理页面的struct page,形成完整的物理内存管理视图。
3.2 struct zone —— 内存域管理单元
struct zone是内核按物理地址范围和NUMA节点划分的内存管理单元,每个zone管理一段连续的物理内存。系统通常包含ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM(32位)或ZONE_MOVABLE(64位)。
| 字段 | 类型 | 说明 |
|---|---|---|
watermark[NR_WMARK] |
unsigned long | 内存水位线:WMARK_MIN、WMARK_LOW、WMARK_HIGH,用于内存压力判断 |
lowmem_reserve[MAX_NR_ZONES] |
long | 为关键分配预留的内存页数,防止低域内存耗尽 |
free_area[MAX_ORDER] |
struct free_area | 伙伴系统空闲页块数组,每个order对应一个链表组 |
lruvec |
struct lruvec | LRU链表向量,管理活跃/非活跃页的回收 |
pageset |
struct per_cpu_pageset | 每CPU页框缓存,减少锁竞争 |
zone_start_pfn |
unsigned long | zone起始物理页帧号 |
spanned_pages |
unsigned long | zone跨越的总页数(含空洞) |
present_pages |
unsigned long | zone实际存在的物理页数 |
managed_pages |
unsigned long | 被伙伴系统管理的页数(present_pages减去预留) |
性能调优要点:水位线
watermark直接影响kswapd回收线程的唤醒频率。在内存密集型场景下,适当提高watermark[WMARK_LOW]可以减少直接内存回收(direct reclaim)的发生次数,降低分配延迟。
3.3 struct free_area —— 空闲页块链表数组
struct free_area是伙伴系统的核心数据结构,每个order对应一个free_area实例,管理该order下所有空闲页块。
// include/linux/mmzone.h
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
关键字段解析:
- free_list[MIGRATE_TYPES]:按迁移类型(MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE、MIGRATE_CMA等)分组的空闲页块链表。每个链表串联相同order的空闲
struct page。 - nr_free:当前order下所有迁移类型的空闲页块总数(注意:是页块数,不是页数)。例如order=2时,nr_free=5表示有5个16KB的连续空闲块。
3.4 struct page —— 物理页描述符
每个物理页框(通常4KB)对应一个struct page实例,它是内核管理物理内存的最小单元。所有struct page构成全局数组mem_map,通过pfn_to_page()宏实现物理页帧号到page结构的转换。
// include/linux/mm_types.h
struct page {
unsigned long flags; // 页状态标志位(PG_locked, PG_dirty, PG_uptodate等)
atomic_t _refcount; // 页引用计数
unsigned int _mapcount; // 页表映射计数
struct list_head lru; // LRU链表节点(用于页面回收)
struct address_space *mapping; // 映射到的地址空间
pgoff_t index; // 在文件中的偏移量
union {
struct { // 用于匿名页
unsigned long private;
struct page *first_page;
};
struct { // 用于slab分配器
struct list_head slab_list;
struct kmem_cache *slab_cache;
};
};
};
| 字段 | 作用 | 性能影响 |
|---|---|---|
flags |
页状态位图,如PG_locked(锁定)、PG_dirty(脏页)、PG_swapcache(交换缓存) | 原子操作频繁,影响多核扩展性 |
_refcount |
内核引用计数,为0时页可被回收 | get_page/put_page开销,大量短生命周期页时显著 |
_mapcount |
用户态页表映射次数,用于判断是否共享页 | fork/COW场景下的关键性能点 |
lru |
链接到zone的LRU链表,用于页面回收 | LRU链表锁竞争是回收路径的常见瓶颈 |
mapping |
指向所属地址空间,区分文件页/匿名页 | 缺页中断时快速判断页类型 |
性能优化关键:
struct page的大小直接影响内存开销。在4KB页框的系统上,每个struct page约64字节,占用物理内存的1.56%(64B/4KB)。对于256GB内存的系统,仅page结构就占用约4GB内存。优化方向包括:使用CONFIG_HUGETLB_PAGE减少page数量,或启用CONFIG_SPARSEMEM_VMEMMAP优化page查找。
3.5 伙伴分配与回收的数据结构操作流程
理解伙伴系统的分配和回收流程,有助于掌握数据结构之间的协作关系。
3.6 关键数据结构关系总结
| 数据结构 | 管理粒度 | 核心作用 | 性能敏感点 |
|---|---|---|---|
struct zone |
内存域(DMA/NORMAL/HIGHMEM) | 内存域级管理,水位线控制,回收触发 | zone->lock竞争,水位线计算 |
struct free_area |
order级(4KB~4MB) | 空闲页块链表,伙伴分配/回收 | 链表操作频繁,迁移类型分组减少碎片 |
struct page |
物理页框(4KB) | 页状态、引用计数、LRU管理 | 原子操作开销,LRU锁竞争 |
常见性能陷阱:当系统内存碎片严重时,
free_area中高阶order的空闲块可能为零,导致分配大块内存时必须触发内存规整(compaction)或直接回收。监控/proc/buddyinfo可以观察各order空闲块分布,若高阶order长期为0,说明碎片问题严重,需考虑启用CONFIG_COMPACTION或调整vm.extfrag_threshold。
3.7 实战:查看伙伴系统数据结构状态
通过以下命令可以实时观察伙伴系统的数据结构状态:
# 查看各zone各order的空闲页块数
cat /proc/buddyinfo
# 输出示例:
# Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
# Node 0, zone DMA32 1234 567 234 89 45 23 12 6 3 2 1
# Node 0, zone Normal 4567 2345 1234 567 234 89 45 23 12 6 3
# 查看各zone的水位线和内存统计
cat /proc/zoneinfo
# 查看全局page分配统计
cat /proc/vmstat | grep -E "pgalloc|pgfree|pgsteal"
解读buddyinfo:每列对应order 0~10,数值表示该order的空闲页块数。例如Normal zone order=0: 4567表示有4567个4KB空闲页块。若高阶order(如order=10)数值为0,说明系统难以分配连续4MB内存。