3. 伙伴系统数据结构

伙伴系统(Buddy System)是Linux内核物理内存管理的核心算法,其高效运作依赖于精心设计的数据结构。本章深入剖析三个关键数据结构:free_area数组、struct page和struct zone,理解它们如何协同完成物理内存的分配与回收。

3.1 核心数据结构总览

伙伴系统的数据结构层次清晰,从全局的struct zone到每个迁移类型的free_area,再到描述每个物理页面的struct page,形成完整的物理内存管理视图。

伙伴系统核心数据结构关系图 struct zone (内存域) struct free_area free_area[MAX_ORDER] struct lruvec lruvec unsigned long watermark[NR_WMARK] free_area[0] (order=0, 4KB) free_area[1] (order=1, 8KB) free_area[2] (order=2, 16KB) ... free_area[MAX_ORDER-1] (order=10, 4MB) struct list_head free_list[MIGRATE_TYPES] unsigned long nr_free struct page (物理页描述符) unsigned long flags atomic_t _refcount unsigned int _mapcount struct list_head lru 关键关系 zone 包含 free_area 数组 free_area 管理空闲 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的连续空闲块。
free_area 内部结构:按迁移类型分组的空闲链表 struct free_area (order = n) free_list[UNMOVABLE] page → page → page free_list[RECLAIMABLE] page → page free_list[MOVABLE] page → page → page → page nr_free = 9 (空闲页块总数) 迁移类型分组优势 • 减少内存碎片 • 提高大块连续内存分配成功率 • 支持内存规整(compaction)

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 伙伴分配与回收的数据结构操作流程

理解伙伴系统的分配和回收流程,有助于掌握数据结构之间的协作关系。

伙伴系统分配与回收流程 分配路径 (alloc_pages) 1. 计算所需order 2. 遍历free_area[order] 3. 找到空闲块? 4. 是:从链表摘除,返回 5. 否:order++,分裂更大块 回收路径 (free_pages) 1. 计算页块order 2. 查找伙伴页块 3. 伙伴空闲且order相同? 4. 是:合并,order++,继续 5. 否:插入free_area[order] 伙伴查找算法 伙伴地址 = 页块起始地址 XOR (1 << (order + PAGE_SHIFT)) 通过异或运算快速定位伙伴,时间复杂度O(1)

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内存。