linux内核 - slab 分配器
一:概述
slab 分配器是 kmalloc()
所依赖的分配器。消除由于内存分配和释放导致的碎片化问题——这种碎片化在使用伙伴系统分配小块内存时尤其明显;加快对常用对象的内存分配速度。
在进行内存分配时,请求的大小会被向上取整为最接近的 2 的幂。伙伴分配器会在对应大小的空闲链表中查找。如果在该链表中没有合适的块,就会从更高一级的链表中取一个条目(该链表中的块大小是前一级的两倍),并将其分成两半(称为 伙伴)。分配器使用其中的一半,而另一半则被放入下一级的链表中。这个过程是 递归的,会一直持续下去,直到伙伴分配器成功找到一个可以拆分的块,或者已经到达最大块大小但仍然没有可用的空闲块为止。
例如,如果最小分配单元是 1KB,而内存总大小是 1MB,那么伙伴分配器会创建多个空闲链表:分别对应 1KB 空闲块、2KB 空闲块、4KB 空闲块、8KB、16KB、32KB、64KB、128KB、256KB、512KB,以及 1MB 空闲块。
现在我们来设想这样一个场景:如果我们想要分配一个 70KB 的内存块,伙伴分配器会将其向上取整为 128KB。接着,它会把 1MB 的内存块拆分成两个 512KB 块,再拆分为 256KB,最后拆分为 128KB。然后,它会将其中一个 128KB 的块分配给用户。
二:slab相关概念
slab中的几个概念:
Slab:由若干页框组成的一块连续物理内存。每个 slab 会被划分为大小相等的若干小块,用来存放特定类型的内核对象,例如 inode 或 mutex 对象。可以把 slab 看作是一个由相同大小块组成的数组。
Cache:由一个或多个 slab 组成的链表。一个 cache 只存放同一类型的对象(例如只存放 inode 对象)。
Slab有以下几种状态:
1. Empty(空):slab 上的所有对象(小块)都被标记为空闲;
2. Partial(部分使用):slab 上同时存在已使用和空闲的对象;
3. Full(已满):slab 上的所有对象都被标记为已使用;
内存分配器负责构建 cache。最初,每个 slab 都是空的,并被标记为 empty。
当为某个内核对象分配内存时,分配器会在该对象类型对应的 cache 中,查找一个 partial 或 free slab 上的空闲位置。如果没有找到,分配器就会分配一个新的 slab,并将其加入到该 cache 中。然后,从这个 slab 中分配对象,并将 slab 标记为 partial。当代码不再使用该内存(即释放内存)时,对象会被简单地返回到 slab cache 中,并保持在初始化状态。这就是为什么内核提供了辅助函数来获取 清零初始化的内存,从而避免残留之前的内容。slab 会维护一个引用计数,用来记录它有多少对象正在被使用。当某个 cache 中的所有 slab 都已满,而又有新的对象请求时,slab 分配器就会负责添加新的 slab。