3. slab分配器:slab/slub/slob对比、kmalloc与kfree、内存池mempool
3.1 为什么需要slab分配器?
讲DMA配置之前,我得先聊聊内核里最常用的内存分配器——slab。你可能会问:内核不是已经有页分配器(buddy system)了吗?为什么还要搞个slab?
原因其实很简单。内核里大量分配的是小对象,比如进程描述符、inode、socket结构体。这些对象通常只有几十到几百字节。如果用页分配器去分配,一个4KB的页只放一个对象,那浪费太严重了。而且频繁的分配释放会导致内存碎片。
slab分配器就是专门解决这个问题的。它把内存划分成一个个"缓存池",每个池子专门存放固定大小的对象。分配和释放都很快,而且不会产生外部碎片。
核心思想:slab分配器是一种"对象缓存"机制。它预先把内存划分成相同大小的对象槽位,分配时直接拿一个槽位,释放时还回去。避免了频繁的页分配操作。
3.2 slab / slub / slob 三兄弟对比
内核里其实有三个分配器实现:slab、slub、slob。它们功能一样,但设计哲学和适用场景不同。我当年刚接触内核时也被这三个名字搞晕过,后来在项目中逐个用过,才算真正理解它们的区别。
| 特性 | slab | slub | slob |
|---|---|---|---|
| 设计年代 | 最早(1994年) | 较新(2007年) | 最简(2005年) |
| 代码复杂度 | 高 | 中 | 低 |
| 内存开销 | 较大(每个CPU维护队列) | 较小 | 极小 |
| 分配速度 | 快(有per-CPU缓存) | 快(简化了per-CPU逻辑) | 慢(链表遍历) |
| 适用场景 | 大型SMP系统 | 大多数通用场景(默认) | 嵌入式/小内存系统 |
| 调试能力 | 强(有red zone检测) | 中 | 弱 |
我个人习惯:在嵌入式Linux开发中,如果内存小于64MB,我会选slob。如果是通用服务器或桌面系统,用slub就对了。slab现在用得少了,除非你在维护老内核或者需要强大的调试功能。
避坑指南:我曾经在一个ARM Cortex-A7的项目上默认用了slub,结果发现内存占用比预期高了不少。后来换成slob,内存占用直接降了30%。所以小内存系统一定要考虑slob。
3.3 kmalloc 与 kfree:最常用的分配接口
kmalloc和kfree是驱动开发中最常用的内存分配函数。它们底层就是基于slab分配器实现的。
#include <linux/slab.h>
void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *objp);
flags参数是重点。它决定了分配行为:
GFP_KERNEL:最常用。可以睡眠,用于进程上下文。GFP_ATOMIC:不会睡眠。用于中断上下文或持有自旋锁时。GFP_DMA:分配DMA可访问的内存(低16MB区域)。GFP_DMA32:分配32位DMA地址空间的内存。
注意:在中断处理函数里千万别用GFP_KERNEL!我见过有人这么写,结果系统直接死锁。中断上下文只能用GFP_ATOMIC。
kmalloc分配的内存物理上是连续的。这对DMA来说很重要,因为DMA控制器通常需要连续的物理地址。不过kmalloc最大只能分配几MB(取决于slab配置),更大的连续内存得用__get_free_pages或dma_alloc_coherent。
3.4 内存池 mempool
mempool是什么?说白了就是一个"应急储备池"。它预先分配一批对象放在池子里,当系统内存紧张时,kmalloc可能失败,但mempool保证你至少能从池子里拿到一个对象。
我当年做网络驱动时遇到过一个问题:在内存压力下,skb分配失败导致网络断连。后来用mempool给skb分配加了一层保障,问题就解决了。
#include <linux/mempool.h>
// 创建内存池
mempool_t *mempool_create(int min_nr,
mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn,
void *pool_data);
// 从池中分配
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);
// 释放回池中
void mempool_free(void *element, mempool_t *pool);
// 销毁内存池
void mempool_destroy(mempool_t *pool);
使用场景:
- 中断处理中必须成功分配内存的地方
- 文件系统元数据分配
- 网络协议栈的关键路径
- DMA描述符分配
经验之谈:mempool不是银弹。它只是保证在内存紧张时不会返回NULL,但池子本身也占用内存。我建议只在"分配失败会导致严重问题"的关键路径上使用。普通的数据缓冲区,用kmalloc就够了。
3.5 知识体系结构图
下面这张图展示了slab分配器、kmalloc/kfree、mempool之间的关系:
3.6 实际项目中的选择建议
说了这么多理论,最后给点实战建议:
- 通用场景:直接用slub,内核默认就是它。kmalloc/kfree随便用。
- 小内存嵌入式:换成slob。记得在内核配置里选CONFIG_SLOB。
- DMA缓冲区:用dma_alloc_coherent,别自己用kmalloc加GFP_DMA。后者在有些架构上不靠谱。
- 关键路径:考虑mempool。但池子大小别设太大,够用就行。
- 调试阶段:打开slub调试(CONFIG_SLUB_DEBUG),能帮你抓到内存越界和释放后使用的问题。
小技巧:想知道当前系统用的是什么分配器?看看/proc/slabinfo是否存在。如果存在且内容详细,就是slab或slub。如果不存在或者内容很少,可能是slob。
嗯,slab分配器这块就聊到这儿。记住一句话:kmalloc是日常,mempool是保险,选对分配器是基本功。