《深入理解Linux内核》Chapter 8 :Linux 内核的内存管理机制

摘要:内存分配、伙伴系统、页面表、内存映射、分区管理、slab 分配器、物理页、虚拟地址、缺页异常、NUMA


一、Linux 内核内存管理概览

Linux 内核通过一套复杂而高效的机制,管理整个系统的物理内存和虚拟地址空间。其核心目标包括:

  • 提供统一的地址抽象(虚拟内存);
  • 支持多进程内存隔离;
  • 高效管理页框资源;
  • 支持动态内存分配(小块/大块);
  • 支持内存映射文件、共享内存、匿名映射;
  • 适配多种架构(x86、ARM、MIPS、RISC-V)和 NUMA 系统。

二、地址空间结构与内存模型

2.1 用户态与内核态地址空间

  • 用户空间:通常 0x00000000 ~ TASK_SIZE(如 0xc0000000);
  • 内核空间:TASK_SIZE ~ 0xFFFFFFFF(或更高地址);
  • 每个进程拥有独立的用户空间,但共享内核地址空间。

2.2 内核虚拟地址空间布局(x86)

区域名称示例地址范围描述
vmalloc 区0xf8000000 ~ 0xfe000000映射非连续页内存
内核模块区0xfe000000 ~ 0xff000000加载 LKM 模块
高端内存映射区0xff000000 ~ 0xffffffff映射高端内存页表
线性映射区0xc0000000 ~ 0xf8000000将物理页框线性映射为虚拟地址

注:不同架构地址范围不同,如 ARM 使用 highmem 更激进划分。


三、物理内存管理:页框与伙伴系统

3.1 页框(Page Frame)

  • Linux 使用固定大小的物理页面(通常为 4KB)作为最小内存单位;
  • 每个物理页对应一个 struct page 结构,记录其状态、使用计数、链表位置等信息。
struct page {
    unsigned long flags;
    atomic_t _count;
    ...
};

3.2 伙伴系统(Buddy Allocator)

Linux 使用伙伴系统管理空闲物理内存页。基本原理:

  • 空闲页框以 2^n 个为一组(称为“阶”);
  • 若 1 页不够,从 2 页、4 页……中拆分;
  • 合并相邻空闲页形成更大块,减少碎片。

常见接口:

__get_free_pages(GFP_KERNEL, order);   // 分配 2^order 页
free_pages(addr, order);              // 释放页面

四、内核动态内存分配:slab/slub/slob

内核中的对象(如 task_struct、inode)大小不一,伙伴系统难以高效管理。Linux 引入 slab 分配器 解决小对象管理问题。

4.1 Slab 分配器原理

  • 基于缓存:每种对象有独立 kmem_cache;
  • 每个 slab 是一组预分配页面;
  • 内核重用对象槽位,避免频繁分配/释放;
  • 支持构造函数(ctor)初始化。

4.2 常用接口

kmem_cache_create();
kmem_cache_alloc();
kmem_cache_free();

也有通用接口:

kmalloc(size, flags);
kfree(ptr);

4.3 SLAB vs SLUB vs SLOB

分配器类型说明适用场景
SLAB原始实现,成熟稳定主流通用
SLUB替代方案,性能优默认(多数发行版)
SLOB精简实现,适合嵌入式资源有限的平台

五、页表与虚拟内存映射

5.1 虚拟地址 → 物理地址的转换

x86 使用四级页表结构:

  • PGD(Page Global Directory)
  • PUD(Page Upper Directory)
  • PMD(Page Middle Directory)
  • PTE(Page Table Entry)

每级页表管理下一层表项指针,最终指向页框物理地址。

转换路径如下:

虚拟地址 → PGD → PUD → PMD → PTE → 物理页框

5.2 页表结构体

pgd_t *pgd = mm->pgd;
p4d_t *p4d = p4d_offset(pgd, vaddr);
pud_t *pud = pud_offset(p4d, vaddr);
pmd_t *pmd = pmd_offset(pud, vaddr);
pte_t *pte = pte_offset_kernel(pmd, vaddr);

六、内存区域与进程地址空间管理

6.1 内存区域(VMA)

每个进程的用户空间被划分为若干虚拟内存区域(vm_area_struct):

  • 每块区域具有统一属性(权限、用途等);
  • 链表/红黑树组织管理;
  • 支持 mmap、brk、共享、匿名映射等。

6.2 mm_struct 结构

struct mm_struct {
    struct vm_area_struct *mmap;
    pgd_t *pgd;
    unsigned long start_brk, brk;
    ...
};

每个进程有一个 mm_struct 管理地址空间,线程共享该结构。


七、缺页异常与页面回收

7.1 缺页异常(Page Fault)

当访问尚未映射的虚拟地址时:

  1. CPU 触发 page fault;
  2. 内核调用 handle_mm_fault();
  3. 分配新页并更新页表;
  4. 如为 mmap 文件,载入对应文件页。

7.2 页面回收(LRU)

系统内存不足时:

  • 启动内存回收线程(kswapd);
  • LRU(最近最少使用)算法选择冷页回收;
  • Active/Inctive 两级页链表管理;

八、高端内存与 kmap

在 32 位系统中,低端内存可直接线性映射,高端内存(highmem) 需手动映射:

void *kmap(struct page *page);
void kunmap(struct page *page);
  • 适用于 DMA 缓冲区、网络接收等;
  • 新系统(64 位)已基本废弃;

九、NUMA 与内存分区

9.1 NUMA 架构

在大型系统中,存在多个内存节点:

  • 每个 CPU 有局部内存(Node);
  • 跨节点访问存在延迟差异;
  • Linux 使用 zoned allocator 管理每个内存节点。

9.2 区域(ZONE)模型

Linux 将物理内存划分为不同 Zone:

ZONE 名称描述
ZONE_DMA为旧设备保留的低端内存区域
ZONE_NORMAL常规内存,线性映射
ZONE_HIGHMEM高端内存,需动态映射(32 位)
ZONE_MOVABLE热插拔可迁移页

十、内核内存调试工具

工具说明
slabinfo查看 SLAB 状态
/proc/meminfo总览系统内存信息
/proc/pagetypeinfo查看不同 zone 页面分布
kmemleak检测内核内存泄露
vmstat虚拟内存状态
buddyinfo伙伴系统空闲页统计

十一、源码文件与结构索引

源码文件路径描述
mm/page_alloc.c页分配器实现
mm/slab.c、mm/slub.cSLAB/SLUB 实现
mm/vmalloc.cvmalloc 区映射实现
mm/fremap.cremap 功能支持
mm/page_fault.c缺页异常处理
include/linux/mm_types.hmm、page、vma 定义

十二、实践建议与实验方向

  1. 编写内核模块申请 kmalloc/kzalloc 内存并打印地址;
  2. 使用 /proc/slabinfo 观察 slab 分配器工作状态;
  3. 使用 vmap 接口映射非连续页内存;
  4. 分析 buddyinfo 内核页框碎片化程度;
  5. 编写用户态 mmap 程序,观察其内存布局;
  6. 使用 strace 捕捉缺页异常带来的系统调用延迟;

十三、总结与对比

机制/结构作用层级或依赖
struct page管理单个物理页框所有物理内存核心结构
Buddy 系统管理连续页分配支撑高效页框回收
SLAB/SLUB小对象缓存内核对象高效分配
vm_area_struct用户进程虚拟内存管理支撑 mmap/anony 区域
页表虚拟地址到物理地址映射实现进程地址隔离
kmap/kmap_atomic高端内存临时映射32 位平台
NUMA非一致内存访问支持多 socket 多节点平台
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐