一、块分配器概述
前面我们知道了页分配器,但是有一定的使用场景不让人满意,因此这里又引伸出了块分配器,这个内存的分配方式有更小的粒度。
SLAB分配器的作用不仅仅是分配小块内存,更重要的作用是针对经常分配和释放的对象充当缓存。
SLAB分配器的核心思想是:为每种对象类型创建一个内存缓存,每个内存缓存由多个大块(slab,原意是大块的混凝土)组成,一个大块是一个或多个连续的物理页,每个大块包含多个对象。
SLAB采用了面向对象的思想,基于对象类型管理内存,每种对象被划分为一类,例如进程描述符(task_struct)是一个类,每个进程描述符实例是一个对象。内存缓存的组成如图3.21所示。
SLAB分配器在某些情况下表现不太好,所以Linux内核提供了两个改进的块分配器。
- (1)在配备了大量物理内存的大型计算机上,SLAB分配器的管理数据结构的内存开销比较大,所以设计了SLUB分配器。
- (2)在小内存的嵌入式设备上,SLAB分配器的代码太多、太复杂,所以设计了一个精简的SLOB分配器。SLOB是“Simple List Of Blocks”的缩写,意思是简单的块链表。目前SLUB分配器已成为默认的块分配器。
二、编程接口
3种块分配器提供了统一的编程接口。
为了方便使用,块分配器在初始化的时候创建了一些通用的内存缓存,对象的长度大多数是2n字节,从普通区域分配页的内存缓存的名称是“kmalloc-”(size是对象的长度),从DMA区域分配页的内存缓存的名称是“dma-kmalloc-”,执行命令“cat /proc/slabinfo”可以看到这些通用的内存缓存。
通用的内存缓存的编程接口如下。
(1)分配内存。
void *kmalloc(size_t size, gfp_t flags);
- size:需要的内存长度。
- flags:传给页分配器的分配标志位,当内存缓存没有空闲对象,向页分配器请求分配页的时候使用这个分配标志位。
块分配器找到一个合适的通用内存缓存:对象的长度刚好大于或等于请求的内存长度,然后从这个内存缓存分配对象。如果分配成功,返回对象的地址,否则返回空指针。
(2)重新分配内存。
void *krealloc(const void *p, size_t new_size, gfp_t flags);
- p:需要重新分配内存的对象。
- new_size:新的长度。
- flags:传给页分配器的标志位。
根据新的长度为对象重新分配内存,如果分配成功,返回新的地址,否则返回空指针。
(3)释放内存。
void kfree(const void *objp);
- objp:kmalloc()返回的对象的地址。
这里提一个思考题:kfree函数怎么知道对象属于哪个通用的内存缓存?后面解答。
特殊接口
1-创建专用的内存缓存
使用通用的内存缓存的缺点是:块分配器需要找到一个对象的长度刚好大于或等于请求的内存长度的通用内存缓存,如果请求的内存长度和内存缓存的对象长度相差很远,浪费比较大,例如申请36字节,实际分配的内存长度是64字节,浪费了28字节。
所以有时候使用者需要创建专用的内存缓存,编程接口如下。
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align,unsigned long flags, void (*ctor)(void *));
- name:名称。
- size:对象的长度。
- align:对象需要对齐的数值。
- flags:SLAB标志位。
- ctor:对象的构造函数。
如果创建成功,返回内存缓存的地址,否则返回空指针。
2-从指定的内存缓存分配对象。
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
- cachep:从指定的内存缓存分配。
- flags:传给页分配器的分配标志位,当内存缓存没有空闲对象,向页分配器请求分配页的时候使用这个分配标志位。
如果分配成功,返回对象的地址,否则返回空指针。
3-释放对象。
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
- cachep:对象所属的内存缓存。
- objp:对象的地址。
4-销毁内存缓存。
void kmem_cache_destroy(struct kmem_cache *s);
- s:内存缓存。
下一篇我们分别来看看块分配器的三种
参考内容《Linux内核深度解析》