1. 内存管理基础
内存管理是操作系统与内核驱动开发的基石。在深入内存泄漏排查之前,必须清晰理解内核如何组织、分配和回收内存。本章将系统梳理物理内存与虚拟内存的核心概念、页框与页表的映射机制,以及两大核心分配器——伙伴系统与slab分配器的设计原理。
1.1 物理内存与虚拟内存
物理内存是硬件实际提供的RAM芯片,而虚拟内存是操作系统为每个进程提供的抽象地址空间。内核通过内存管理单元(MMU)将虚拟地址转换为物理地址,实现进程隔离、地址空间扩展和内存保护。
核心要点: 驱动开发中,你操作的是虚拟地址(如 kmalloc 返回的指针),但最终数据必须落在物理页框上。理解这一层映射是调试内存问题的前提。
| 概念 | 定义 | 驱动开发中的意义 |
|---|---|---|
| 物理内存 | 硬件RAM,以字节为单位,地址连续 | DMA操作、页框分配、硬件寄存器访问 |
| 虚拟内存 | 进程/内核看到的线性地址空间 | kmalloc/vmalloc返回的地址,通过页表映射 |
| MMU | 硬件单元,执行地址转换 | 缺页异常、TLB刷新影响驱动性能 |
| 地址空间 | 用户空间(0~3G) + 内核空间(3G~4G) | 驱动运行在内核空间,需注意地址合法性 |
1.2 页框与页表
物理内存被划分为固定大小的页框(Page Frame),通常为4KB。虚拟内存同样被划分为相同大小的页(Page)。页表就是虚拟页到物理页框的映射表。
- 页框(Page Frame):物理内存的最小分配单元,由
struct page结构体描述,内核维护全局的mem_map数组。 - 页表(Page Table):多级结构(如x86的4级页表),每级表项指向下一级或物理页框。
- TLB(Translation Lookaside Buffer):MMU内部的页表缓存,驱动中频繁切换地址空间时需注意TLB刷新开销。
调试提示: 使用
cat /proc/$(pid)/maps 查看进程虚拟内存布局;使用 cat /proc/meminfo 查看系统物理内存使用情况。驱动中可通过 __pa() 和 __va() 在虚拟地址与物理地址之间转换。
1.3 伙伴系统(Buddy System)
伙伴系统是内核管理物理页框的核心算法,用于分配和释放连续物理内存。它将空闲页框按2的幂次(order)组织成链表,分配时从最小满足的order中拆分,释放时合并相邻的“伙伴”页框。
| Order | 页框数量 | 大小 | 典型用途 |
|---|---|---|---|
| 0 | 1 | 4KB | 普通页分配 |
| 1 | 2 | 8KB | 小规模DMA缓冲区 |
| 2 | 4 | 16KB | 网络协议栈 |
| 3 | 8 | 32KB | 文件系统缓存 |
| 4 | 16 | 64KB | 中等DMA缓冲区 |
| 9 | 512 | 2MB | 大页分配(HugeTLB) |
| 10 | 1024 | 4MB | 内核映像、大块连续内存 |
⚠️ 驱动开发陷阱: 频繁调用
__get_free_pages(GFP_KERNEL, order) 分配高阶order(order≥3)容易导致内存碎片化,长期运行后可能分配失败。优先使用 slab 分配器或 kmalloc 处理小对象。
1.4 Slab分配器
Slab分配器建立在伙伴系统之上,专门用于管理内核中频繁分配/释放的小对象(如 task_struct、inode、dentry)。它通过缓存预分配的对象来减少伙伴系统的碎片化和分配开销。
- Slab缓存(kmem_cache):每个缓存管理一种特定大小的对象,如
kmalloc-32、kmalloc-64。 - Slab节点:每个缓存包含多个slab,每个slab由一个或多个连续页框组成,内部划分为对象槽。
- 三种状态:slab可以是 满(Full)、部分满(Partial)、空(Empty),分配优先从Partial中取。
- kmalloc:基于slab分配器的通用接口,驱动中最常用的内存分配函数。
驱动最佳实践: 如果驱动需要频繁分配同一结构体(如网络描述符、USB请求块),应使用
kmem_cache_create() 创建专用缓存,而非直接使用 kmalloc。这能显著减少内存碎片并提升性能。
1.5 内存分配路径总结
驱动开发中,理解内存分配的全路径有助于定位性能瓶颈和内存泄漏源头。以下表格总结了不同分配接口的底层路径:
| 驱动接口 | 底层分配器 | 分配单位 | 典型场景 |
|---|---|---|---|
kmalloc(size, flags) |
Slab分配器 | 对象(8B~8KB) | 通用小内存分配 |
kzalloc(size, flags) |
Slab分配器 | 对象(清零) | 需要初始化为0的结构体 |
kmem_cache_alloc(cache, flags) |
Slab分配器 | 固定大小对象 | 高频分配相同结构体 |
__get_free_pages(order, flags) |
伙伴系统 | 2^order个页框 | 大块连续物理内存 |
vmalloc(size) |
伙伴系统 + 页表映射 | 页框(非连续虚拟地址) | 大块虚拟连续内存 |
dma_alloc_coherent(dev, size, ...) |
伙伴系统 + 一致性映射 | 连续物理页框 | DMA缓冲区 |
学习路径提示: 本章建立的内存管理基础概念(虚拟地址映射、页框、伙伴系统、slab)是后续理解 kmemleak 工作原理的前提。kmemleak 正是通过扫描伙伴系统和 slab 缓存中的对象引用来检测孤立内存块。下一章将深入 kmemleak 的启用与配置。