【Note】《深入理解Linux内核》Chapter 8 :Linux 内核的内存管理机制
·
《深入理解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)
当访问尚未映射的虚拟地址时:
- CPU 触发 page fault;
- 内核调用
handle_mm_fault(); - 分配新页并更新页表;
- 如为 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.c | SLAB/SLUB 实现 |
mm/vmalloc.c | vmalloc 区映射实现 |
mm/fremap.c | remap 功能支持 |
mm/page_fault.c | 缺页异常处理 |
include/linux/mm_types.h | mm、page、vma 定义 |
十二、实践建议与实验方向
- 编写内核模块申请
kmalloc/kzalloc内存并打印地址; - 使用
/proc/slabinfo观察 slab 分配器工作状态; - 使用
vmap接口映射非连续页内存; - 分析
buddyinfo内核页框碎片化程度; - 编写用户态 mmap 程序,观察其内存布局;
- 使用
strace捕捉缺页异常带来的系统调用延迟;
十三、总结与对比
| 机制/结构 | 作用 | 层级或依赖 |
|---|---|---|
struct page | 管理单个物理页框 | 所有物理内存核心结构 |
| Buddy 系统 | 管理连续页分配 | 支撑高效页框回收 |
| SLAB/SLUB | 小对象缓存 | 内核对象高效分配 |
vm_area_struct | 用户进程虚拟内存管理 | 支撑 mmap/anony 区域 |
| 页表 | 虚拟地址到物理地址映射 | 实现进程地址隔离 |
| kmap/kmap_atomic | 高端内存临时映射 | 32 位平台 |
| NUMA | 非一致内存访问支持 | 多 socket 多节点平台 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)