Linux内核内存管理:源码组成、设计原理与关键点

2 阅读8分钟

一、整体架构概览

内存管理子系统层次结构

用户空间进程
    │
    ▼
系统调用接口
    │
    ├── mmap() ──┐
    ├── brk()   │
    ├── malloc()│
    └── 其他内存相关系统调用
    │
    ▼
虚拟内存管理器 (Virtual Memory Manager)
    ├── 地址空间管理 (mm_struct)
    ├── 虚拟内存区域管理 (vm_area_struct)
    ├── 页表管理
    └── 缺页异常处理
    │
    ▼
物理内存管理器
    ├── 伙伴系统 (Buddy System)
    ├── SLAB/SLUB分配器
    ├── 每CPU页框缓存
    └── 内存区域管理 (ZONE)
    │
    ▼
硬件抽象层
    ├── MMU接口
    ├── TLB管理
    └── 架构特定代码

二、核心数据结构与源码组成

1. 物理页框管理 (include/linux/mm_types.h)

struct page {
    unsigned long flags;            /* 页框状态标志 */
    union {
        struct {    /* 页框缓存和匿名页 */
            struct list_head lru;   /* LRU链表 */
            struct address_space *mapping;  /* 地址空间 */
            pgoff_t index;          /* 页内偏移 */
            void *private;          /* 私有数据 */
        };
        // ... 其他联合体成员
    };
    atomic_t _refcount;             /* 引用计数 */
    atomic_t _mapcount;             /* 页表映射计数 */
    unsigned long compound_head;    /* 复合页头 */
    unsigned int compound_order;    /* 复合页阶数 */
};

2. 内存区域划分 (include/linux/mmzone.h)

/* 内存区域类型 */
enum zone_type {
    ZONE_DMA,           /* DMA区域 */
    ZONE_DMA32,         /* 32位DMA区域 */
    ZONE_NORMAL,        /* 普通内存区域 */
    ZONE_HIGHMEM,       /* 高端内存 */
    ZONE_MOVABLE,       /* 可移动区域 */
    ZONE_DEVICE,        /* 设备内存 */
    __MAX_NR_ZONES
};

/* 区域描述符 */
struct zone {
    unsigned long watermark[NR_WMARK];  /* 水位线 */
    long lowmem_reserve[MAX_NR_ZONES];  /* 保留内存 */
    
    struct pglist_data *zone_pgdat;     /* 所属节点 */
    struct per_cpu_pageset __percpu *pageset;  /* 每CPU页框缓存 */
    
    /* 空闲区域管理 */
    struct free_area free_area[MAX_ORDER];
    
    /* 统计信息 */
    atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
    atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
    
    unsigned long zone_start_pfn;      /* 起始页框号 */
    unsigned long managed_pages;       /* 管理页数 */
    unsigned long spanned_pages;       /* 跨越页数 */
    unsigned long present_pages;       /* 实际页数 */
    
    const char *name;                  /* 区域名称 */
    wait_queue_head_t *wait_table;     /* 等待队列 */
    // ...
};

3. NUMA节点管理

typedef struct pglist_data {
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];
    
    int nr_zones;
    unsigned long node_start_pfn;
    unsigned long node_present_pages;
    unsigned long node_spanned_pages;
    
    int node_id;
    struct pglist_data *node_next;
    
    /* 内存统计 */
    struct lruvec lruvec;
    
    /* 回收相关 */
    unsigned long       min_unmapped_pages;
    unsigned long       min_slab_pages;
    
    /* 每节点页框缓存 */
    struct per_cpu_nodestat __percpu *per_cpu_nodestats;
} pg_data_t;

4. 地址空间管理 (include/linux/mm_types.h)

struct mm_struct {
    struct vm_area_struct *mmap;        /* VMA链表 */
    struct rb_root mm_rb;               /* VMA红黑树 */
    
    unsigned long start_code, end_code; /* 代码段边界 */
    unsigned long start_data, end_data; /* 数据段边界 */
    unsigned long start_brk, brk;       /* 堆边界 */
    unsigned long start_stack;           /* 栈起始 */
    
    unsigned long arg_start, arg_end;    /* 命令行参数 */
    unsigned long env_start, env_end;    /* 环境变量 */
    
    unsigned long total_vm;              /* 总虚拟内存 */
    unsigned long locked_vm;             /* 锁定内存 */
    unsigned long pinned_vm;             /* 固定内存 */
    unsigned long data_vm;               /* 数据内存 */
    unsigned long exec_vm;               /* 可执行内存 */
    unsigned long stack_vm;              /* 栈内存 */
    
    spinlock_t page_table_lock;          /* 页表锁 */
    struct rw_semaphore mmap_sem;        /* MMAP信号量 */
    
    pgd_t *pgd;                          /* 页全局目录 */
    
    atomic_t mm_users;                   /* 用户计数 */
    atomic_t mm_count;                   /* 引用计数 */
    
    /* 反向映射 */
    struct {
        struct vm_area_struct *mmap;     /* 链表 */
        struct rb_root rb_root;         /* 红黑树 */
    } mm_mt;
    
    // ...
};

5. 虚拟内存区域 (include/linux/mm_types.h)

struct vm_area_struct {
    unsigned long vm_start;             /* 起始地址 */
    unsigned long vm_end;               /* 结束地址 */
    
    struct mm_struct *vm_mm;           /* 所属地址空间 */
    pgprot_t vm_page_prot;              /* 访问权限 */
    unsigned long vm_flags;             /* 标志位 */
    
    struct rb_node vm_rb;               /* 红黑树节点 */
    
    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } shared;
        struct anon_vma_name *anon_name;
    };
    
    struct list_head anon_vma_chain;    /* 匿名VMA链 */
    struct anon_vma *anon_vma;          /* 匿名VMA */
    
    const struct vm_operations_struct *vm_ops;  /* 操作函数 */
    unsigned long vm_pgoff;             /* 文件偏移 */
    struct file *vm_file;              /* 映射文件 */
    void *vm_private_data;             /* 私有数据 */
    
    struct vm_area_struct *vm_next, *vm_prev;  /* 链表 */
};

三、关键设计原理

1. 三级内存分配机制

1.1 伙伴系统 (mm/page_alloc.c)

/* 伙伴系统核心数据结构 */
struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];  /* 空闲链表 */
    unsigned long       nr_free;                   /* 空闲页数 */
};

设计原理

  • 将空闲内存分为11个不同阶的链表(0-10阶,对应1, 2, 4, ..., 1024个连续页)
  • 分配时从合适阶的链表获取,如果没有则从更高阶拆分
  • 释放时检查相邻块是否空闲,如空闲则合并

1.2 SLAB分配器 (mm/slab.c, mm/slub.c, mm/slob.c)

struct kmem_cache {
    unsigned int        size;           /* 对象大小 */
    unsigned int        object_size;     /* 实际对象大小 */
    unsigned int        offset;          /* 空闲指针偏移 */
    
    struct kmem_cache_node *node[MAX_NUMNODES];
    struct array_cache  *cpu_cache;     /* 每CPU缓存 */
    
    /* SLAB管理 */
    unsigned int        colour;          /* 缓存行着色 */
    unsigned int        colour_off;      /* 着色偏移 */
    unsigned int        num;             /* 每SLAB对象数 */
    
    /* 构造函数/析构函数 */
    void (*ctor)(void *obj);
    // ...
};

设计原理

  • SLAB:为内核对象提供缓存,减少内存分配开销
  • SLUB:简化设计,更好性能,默认分配器
  • SLOB:适用于嵌入式系统,简单但碎片多

2. 按需分页与写时复制

2.1 缺页异常处理 (mm/memory.c)

static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{
    if (!vmf->pte) {
        if (vma_is_anonymous(vmf->vma))
            return do_anonymous_page(vmf);    /* 匿名页 */
        else
            return do_fault(vmf);              /* 文件映射 */
    }
    
    if (vmf->flags & FAULT_FLAG_WRITE) {
        if (!pte_write(entry))
            return do_wp_page(vmf);            /* 写时复制 */
        entry = pte_mkdirty(entry);
    }
    // ...
}

2.2 写时复制实现

static vm_fault_t do_wp_page(struct vm_fault *vmf)
{
    struct vm_area_struct *vma = vmf->vma;
    struct page *old_page, *new_page;
    
    /* 获取旧页 */
    old_page = vm_normal_page(vma, vmf->address, vmf->pte);
    
    /* 如果是零页或独占映射,直接设置可写 */
    if (PageAnon(old_page) && PageAnonExclusive(old_page)) {
        ptep_set_wrprotect(vma->vm_mm, vmf->address, vmf->pte);
        return 0;
    }
    
    /* 分配新页 */
    new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vmf->address);
    
    /* 复制内容 */
    copy_user_highpage(new_page, old_page, vmf->address, vma);
    
    /* 建立新映射 */
    ptep_clear_flush(vma, vmf->address, vmf->pte);
    page_add_new_anon_rmap(new_page, vma, vmf->address, false);
    set_pte_at(vma->vm_mm, vmf->address, vmf->pte, 
               mk_pte(new_page, vma->vm_page_prot));
    
    /* 释放旧页引用 */
    put_page(old_page);
    return 0;
}

3. 内存回收机制 (LRU算法)

3.1 LRU链表管理 (mm/vmscan.c)

enum lru_list {
    LRU_INACTIVE_ANON = LRU_BASE,
    LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
    LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
    LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
    LRU_UNEVICTABLE,
    NR_LRU_LISTS
};

设计原理

  • 双LRU链表:活跃链表和非活跃链表
  • 页面访问时移动到活跃链表尾部
  • 回收时从非活跃链表头部选择页面

3.2 反向映射 (Reverse Mapping)

struct anon_vma {
    struct rw_semaphore rwsem;      /* 读写信号量 */
    atomic_t refcount;              /* 引用计数 */
    
    struct anon_vma *root;          /* 根节点 */
    struct rb_root_cached rb_root;  /* 红黑树根 */
};

作用:快速找到映射到某个物理页的所有页表项,用于页面回收和迁移

4. 透明大页 (THP) 支持

4.1 大页分配 (mm/huge_memory.c)

static int do_huge_pmd_anonymous_page(struct vm_fault *vmf)
{
    struct vm_area_struct *vma = vmf->vma;
    gfp_t gfp;
    
    /* 检查是否启用THP */
    if (!transparent_hugepage_enabled(vma))
        return VM_FAULT_FALLBACK;
    
    /* 分配大页 */
    gfp = alloc_hugepage_direct_gfpmask(vma);
    page = alloc_hugepage_vma(gfp, vma, haddr, HPAGE_PMD_ORDER);
    
    /* 清零大页 */
    clear_huge_page(page, vmf->address, HPAGE_PMD_NR);
    
    /* 建立映射 */
    set_huge_zero_page(pgtable, vma, page, haddr);
    
    return 0;
}

四、关键点说明

1. 内存分配策略

1.1 GFP标志 (include/linux/gfp.h)

/* 分配器标志 */
#define GFP_KERNEL       __GFP_RECLAIM
#define GFP_ATOMIC       (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_USER         __GFP_WAIT
#define GFP_HIGHUSER     (GFP_USER | __GFP_HIGHMEM)
#define GFP_NOFS         (__GFP_WAIT | __GFP_IO)
#define GFP_NOIO         __GFP_WAIT

1.2 水位线机制

enum zone_watermarks {
    WMARK_MIN,      /* 最低水位线 - 开始回收 */
    WMARK_LOW,      /* 低水位线 - 开始压缩 */
    WMARK_HIGH,     /* 高水位线 - 停止回收 */
    NR_WMARK
};

2. 内存压缩与碎片整理

2.1 内存压缩 (mm/compaction.c)

static int compact_zone(struct zone *zone, struct compact_control *cc)
{
    while ((ret = isolate_migratepages(cc)) == ISOLATE_ABORT) {
        migrate_pages(&cc->migratepages, compaction_alloc,
                      compaction_free, (unsigned long)cc,
                      cc->mode, MR_COMPACTION);
    }
}

2.2 内存碎片整理策略

  • 内存规整:移动页面以创建连续大块内存
  • 页迁移:迁移正在使用的页面
  • 回收不可移动页面:尝试回收锁定页面

3. NUMA优化

3.1 NUMA感知分配

static struct page *alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
                     int preferred_nid, nodemask_t *nodemask)
{
    /* 优先从本地节点分配 */
    if (preferred_nid >= 0)
        page = __alloc_pages(gfp_mask, order, 
                             zonelist_zone_idx(zonelist, preferred_nid));
    
    /* 回退到其他节点 */
    if (!page)
        page = __alloc_pages(gfp_mask, order, NUMA_NO_NODE);
    
    return page;
}

4. 内存控制组 (Cgroups) 集成

4.1 内存控制器 (mm/memcontrol.c)

struct mem_cgroup {
    struct page_counter memory;          /* 内存使用 */
    struct page_counter swap;            /* 交换空间 */
    struct page_counter kmem;            /* 内核内存 */
    
    /* 每节点统计 */
    struct mem_cgroup_per_node *nodeinfo[MAX_NUMNODES];
    
    /* 回收参数 */
    unsigned long high;                   /* 高水位线 */
    unsigned long low;                    /* 低水位线 */
    unsigned long min;                    /* 最小值 */
    
    /* OOM控制 */
    oom_control_t oom;
};

五、重要源码文件说明

mm/
├── page_alloc.c           # 伙伴系统实现
├── slab.c                 # SLAB分配器
├── slub.c                 # SLUB分配器
├── vmscan.c               # 页面回收
├── memory.c               # 核心内存管理
├── mmap.c                 # 内存映射
├── mlock.c                # 内存锁定
├── mremap.c               # 内存重映射
├── swap.c                 # 交换管理
├── swap_state.c           # 交换状态
├── swapfile.c             # 交换文件
├── vmalloc.c             # 虚拟内存分配
├── hugetlb.c             # 大页支持
├── huge_memory.c         # 透明大页
├── compaction.c          # 内存压缩
├── memcontrol.c          # 内存控制组
├── mempool.c             # 内存池
├── kmemleak.c            # 内存泄漏检测
└── ksm.c                 # 内核同页合并

六、调优参数与性能优化

1. 内核参数调优

# /proc/sys/vm/ 下重要参数
vm.swappiness = 60        # 交换倾向
vm.vfs_cache_pressure = 100 # 文件缓存压力
vm.dirty_ratio = 20        # 脏页比例阈值
vm.dirty_background_ratio = 10  # 后台回写阈值
vm.min_free_kbytes = 67584  # 最小空闲内存
vm.watermark_scale_factor = 10  # 水位线缩放因子

2. 透明大页配置

# 查看THP状态
cat /sys/kernel/mm/transparent_hugepage/enabled

# 配置选项
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/enabled

3. NUMA策略

# 查看NUMA状态
numastat
numactl --hardware

# 设置NUMA策略
numactl --cpunodebind=0 --membind=0 ./program

七、总结

Linux内核内存管理是一个复杂而精密的系统,其设计特点包括:

  1. 层次化设计:从硬件抽象到用户接口的清晰分层
  2. 按需分配:延迟分配和写时复制提高内存利用率
  3. 缓存优化:多级缓存结构减少分配开销
  4. 智能回收:基于LRU的回收算法平衡性能与内存使用
  5. NUMA感知:优化非均匀内存访问架构性能
  6. 容器支持:通过Cgroups提供资源隔离

理解这些设计原理对于系统调优、性能分析和问题排查至关重要。在实际应用中,需要根据具体工作负载调整内存管理参数,以达到最佳性能。