支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
Linux 内核的内存管理子系统是操作系统中最复杂、最核心的组成部分之一。无论是用户程序运行的虚拟地址空间,还是内核中各种驱动、数据结构的内存分配,其底层都依赖于精巧设计的内存管理机制。本文将围绕用户空间与内核空间的差异、核心概念、关键机制与典型实战问题,逐步构建起清晰完整的知识图谱。
一、用户空间与内核空间的内存区别
Linux 将整个虚拟地址空间划分为两个区域:
维度 | 用户空间 | 内核空间 |
---|---|---|
地址范围 | 通常 0x00000000 ~ 0xBFFFFFFF(32位) | 通常 0xC0000000 ~ 0xFFFFFFFF |
访问权限 | 仅用户态进程可访问 | 仅内核态代码可访问 |
分配方式 | malloc(), mmap() 等 | kmalloc(), vmalloc(), alloc_pages() 等 |
分配器 | 用户态使用 glibc 封装(底层调用 sys_brk/sys_mmap) | 使用内核页帧分配器、slab/slub 等 |
是否可以直接访问物理地址 | ❌ 不可 | ✅ 通过页表间接访问 |
二、核心内存管理机制概览
Linux 内核内存管理由多个机制协同组成,每种机制负责不同粒度或阶段的内存操作。
1. 页帧管理机制(Page Frame Allocator)
- 单位:以页为单位分配内存,页大小通常为 4KB(x86/ARM)。
- 基本机制:伙伴系统(Buddy Allocator)
- 管理结构:struct page
2. 内核小块内存管理:Slab/Slub
- 适用于频繁申请释放的小对象,如 task_struct、inode、dentry。
- 分配单位:object,而不是 page。
- SLAB -> SLUB(现代默认)
3. memblock(启动早期临时管理器)
- 只在启动阶段使用,未建立起完整页表前。
- 后续交由 buddy 系统管理。
4. CMA(Contiguous Memory Allocator)
- 作用:为 DMA 提供连续的物理内存。
- 使用场景:GPU、ISP、音视频 DMA 需求。
5. vmalloc
- 虚拟地址连续,但物理地址不一定连续。
- 适合分配大块内存,但开销比 kmalloc 大。
6. percpu 分配器
- 为每个 CPU 分配一份对象副本,避免锁争用。
三、页表结构解析与地址转换
在分页机制下,虚拟地址需经过页表转换为物理地址。
1. 多级页表结构(以 64 位架构为例):
VA (虚拟地址) ➔ PGD ➔ PUD ➔ PMD ➔ PTE ➔ 页框
缩写 | 含义 |
---|---|
PGD | Page Global Directory |
PUD | Page Upper Directory |
PMD | Page Middle Directory |
PTE | Page Table Entry |
每级结构索引不同位数,共同组成完整地址映射关系。
2. TLB(Translation Lookaside Buffer)
- 硬件缓存页表项,加快地址转换。
- 命中:无需访问内存
- 未命中:需访问页表(页表项位于内存中)
四、物理内存架构模型
三种主流模型总结:
模型 | 宏定义 | 特点 | 典型平台 |
---|---|---|---|
Flat Memory Model | CONFIG_FLATMEM | 连续物理内存,无洞洞 | 嵌入式 SoC、低端 ARM |
Discontiguous Model | CONFIG_DISCONTIGMEM | 多 bank,存在间隙 | NUMA 早期 SMP 系统 |
Sparse Memory Model | CONFIG_SPARSEMEM | Section 管理,支持热插拔 | 大内存 NUMA 系统,x86_64 |
五、结构体关系解读
1. struct pglist_data(每个 NUMA 节点)
struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
...
};
2. struct zone(内存管理区:DMA, Normal, HighMem)
struct zone {
struct page *mem_map; // 页框描述符数组
...
};
3. struct page(物理页描述符)
struct page {
unsigned long flags;
struct address_space *mapping;
void *virtual; // 页映射后的虚拟地址(有条件存在)
...
};
结构关系图:
[Node] struct pglist_data
└─ zone[N] // struct zone
└─ page[M] // struct page
六、典型实战问题与解答
问题 1:用户程序频繁崩溃,是否与内存分配相关?
定位方法:
- 使用
strace
查看是否 brk/mmap 系统调用失败 - dmesg 查看是否存在 OOM 杀手信息
根因分析:
- 用户空间 malloc 失败底层反映内核页分配失败(如内存碎片、zonelist 无法满足)
解决建议:
- 优化分配粒度,复用内存
- 调整内核参数或限制内存使用
问题 2:驱动中 kmalloc 分配失败如何排查?
观察点:
- slabinfo (
cat /proc/slabinfo
) - 分配大小是否超过
kmalloc-512
,kmalloc-1024
类缓存池
建议使用:
- 过大则使用
vmalloc()
或 CMA
七、经典概念类问题总结
-
什么是 struct page?为什么不是每页都需要一个?
- 用于记录每个页框的状态信息,如引用计数、所属 zone。
- 某些内存区域(如设备保留内存)无需 struct page 管理。
-
什么是 memblock?
- 内核早期内存管理器,在 MMU 启动前分配内存;之后由 buddy 管理接管。
-
CMA 和 Buddy 的关系?
- CMA 是 Buddy 的一个子区域,标记为“可回收但保留连续性”。
-
vmalloc 与 kmalloc 的本质区别?
- kmalloc 分配的是物理地址连续的内存。
- vmalloc 虚拟地址连续,物理地址可能不连续。
-
SLAB/SLUB 区别?
- SLUB 是对 SLAB 的改进,结构更简洁,锁竞争更少,是当前主流选择。
八、总结:构建清晰的知识体系
通过上述内容,可以构建出 Linux 内存管理机制的知识地图:
+---------------------------+
| 用户态分配(malloc/mmap) |
+-------------+-------------+
|
+----------v----------+
| 内核系统调用接口层 |
+----------+----------+
|
+---------------v----------------+
| 页帧分配器(Buddy) |
| + memblock/CMA/SLUB 等子模块 |
+--------------------------------+
|
+-------v--------+
| 物理内存页表映射 |
+----------------+
九、下一步学习建议
- 阅读 mm/page_alloc.c 理解页面分配流程
- 阅读 mm/slub.c 了解内核对象管理逻辑
- 自行实现一个 kmalloc 模拟器或 slab 分配器
- 实战调试:结合
ftrace
+perf
+kmemleak
检查内存行为
📌 博文结尾建议:
本文为 Linux 内核内存管理核心知识总结,涉及了从用户空间申请,到页表转换,再到内核多种分配器的使用场景与机制。如果你对性能优化、内存泄漏排查、驱动开发感兴趣,这些内容将是你不可缺的基础。
支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》