# slub的理念

slub分配器对一个内核开发者来讲是既熟悉又陌生的。

熟悉是因为在开发的过程中大家总会使用到它，什么kmem\_cache\_alloc(), kmalloc()都是slub分配器的接口。而陌生是因为大部分开发者都不了解slub分配器的工作机制。像页分配器大家至少还听说过伙伴系统，而slub分配器好像真的一点绯闻都没有。

那今天我就尝试用我这粗陋的认知给大家揭开一点点盖头来。

## 从设计理念开始

> 假装自己懂得很多的样子，给大家讲讲我对内存模块的理解。

内存模块就是一个大管家，管理着系统中内存的使用。有人要用内存了，它分配一点。有人释放内存了，它小心翼翼收好。

在整个模块中，我们能看到其运用了三个设计思想。

第一个思想就是分层。通过之前的文章分析我们也能看到在x86架构上分成了:e820, memblock, page, slub这么几层。分层设计的思想在计算机业十分普遍，其优势也自然用不着我说。

第二个思想我认为是**分类管理**。

这个思想从页分配器这一层就开始了。为了更好的管理内存，在页分配器这一层，页就依照NUMA和ZONE的属性分类，在不同需求时分配不同的页。而且页还依照他连续空闲的大小分类，这样对不同大小的内存请求也可以快速找到空闲页。slub中也是这种思想，

> 其做法就是事先分配好指定大小的**内存板**，当有内存请求时，直接在指定的内存板中分配和释放。

而这个思想在我们生活中也有用武之地，当遇到比较多的物品时，就可以分类存放方便使用。比如忘了在哪个节目中听说有女明星有上百双鞋放满了一个屋子，那这个时候就可以按照运动鞋，高跟鞋，休闲鞋等分类。否则找鞋所花费的检索时间还不如直接网购一双来的快。

第三个思想是预取，也就是事先准备好一些资源而不是每次等有请求时再去分配。

这个思想也很常见，比如说cpu上的cache。当每次读取一段数据的时候，把其后的一段内容也加载进cache。根据概率统计，在读取一部分内容后有很大概率会访问之后的部分内容，所以这样的设计可以提升性能。（PS：这也导致了各种性能优化的小窍门）

slub分配器也使用了这样的思想。在每一个分类的内存板中，都预先保存了空闲的内存以便于快速响应。

好了，吹牛的部分讲完了，接下来就看看slub是究竟如何做到分类管理和预取的。

## 分类管理

在slub分配器中，用来做分类管理的就是这个kmem\_cache结构体了。

```
kmem_cache                      
+------------------------------+
|name                          |
|    (char *)                  |
+------------------------------+
|object_size                   |   = original object size
|inuse                         |   = ALIGN(object_size, sizeof(void *))
|size                          |   = ALIGN(inuse + padding + debug space, s->align)
|align                         |
|offset                        |
|    (int)                     |
|reserved                      |
+------------------------------+
|oo                            |
|min                           |
|max                           |
|   (kmem_cache_order_objects) |
|   +--------------------------+
|   |order                     |
|   |order_objects             |
+---+--------------------------+
```

说到分类，我们总是按照某些属性分的，比如说鞋子的用途。那在slub中按照什么属性分类呢？

首先就是按照名字了。所以我们看到在调用kmem\_cache\_create()的第一个参数就是name，而这个名字就保存在了kmem\_cache中的name字段。我们可以通过

```
cat /proc/slabinfo
```

来查看系统中slub的分类。比如会有常见的task\_struct, inode\_cache等。

另一种是按照大小分类。我们可以在上面命令的输出中看到kmalloc-512， kmalloc-256等字样。这就是那些没有特定名字，按照大小来分配时选用的内存板。

其实按照名字分类时隐含了按大小分类的意思，这里单独列出是为了引出slub中对大小的一个计算，也就是按照什么样的标准进行预取。

## 预取

预取的目的是为了能够提高系统的性能，那它是如何做到这一点的呢？我们来看看生活中的例子。

比如在超市中，我们看到货架上琳琅满目的商品，这都可以算是预取。超市按照估计预先把商品放在货架上，等待顾客的购买。好像没有见过哪个超市是每个商品只摆放一个的吧？如果每种商品货架上只有一个，那么如果有几个人要购买，还不要等很久？

> 预取首先节省了顾客的平均购买时间。

接下来我们再来看看另一头，服务员的工作。顾客在购买的过程中，服务员也会不断地补充货架。但是从仓库取出商品，到摆放到货架需要一定的时间。如果每当一个商品被拿走就去仓库取出一个商品补充，那是不是太琐碎了呢？如果有哪个超市是这么做的，那么这个超市肯定要倒闭。所以超市中改进的方案是，当货架上的货物基本卖完了后，才去仓库取出**一批**货物补充货架。

> 预取其次降低了服务员的工作负担。

道理都是懂的，但是落到操作上就有一个实际问题需要解决--一批是多少？拿少了不能最大化每次取货的劳动，拿多了货架上放不下。那如果是你，你会如何定义“一批”的数量呢？

> 如果是我的话，我可能会选择补满一货架

**生活中如此，内核中也是如此。**

内存管理模块是层次化的，slub分配器建立在页分配器上，所以可以牵强的理解为**页是slub的货架**。

超市中货物上架前需要做好两个计算：

* 货架的大小
* 货架上能放多少货物

在slub中同样要计算相应的两个值：

* 用多大的页来作为货架
* 每个页中可以放多少object

这两个数据都保存在上图kmem\_cache结构体中的oo字段。整个计算过程在calculate\_sizes()函数中，图表中其余字段在计算过程中各有用途。

PS：上面说到的页不是单个的物理页，而是内核struct page对应页的概念。

来举两个例子说明一下问题：

* 假如想要申请的结构体（货物）大小是512字节，那么页（货架）可以选择为4K字节大小，每个页（货架）上就可以存放8个结构体（货物）。
* 假如想要申请的结构体（货物）大小是2050字节，那么页（货架）可以选择为8K字节大小，每个页（货架）上就可以存放3个结构体（货物）。

当然在实际计算的时候第二种情况的值可能不是这样，因为大家可以看到这么选择其实会有较大的浪费，内核很有可能选择更大的页来减少内存浪费。

好了，希望本文对大家理解slub有一点点的帮助。slub依然博大精深，还有很多非常巧(nue)妙(xin)的设计。有兴趣的童鞋做好心理准备～
