前一篇讲了SLAB的基本原理,本来这篇打算写SLUB的原理。但在CSDN中发现了一篇非常好的描述SLUB原理的文章。链接:https://blog.csdn.net/lukuen/article/details/6935068
重复造轮子没必要,且就算重新可能也没他写得好。本着拿来主义,直接参考之。但是为了帮助自己以后快速回顾SLUB的原理,这里重点记录下kmem_cache_node这种cache的创建以及分配过程,以及kmalloc基于slub的实现,最后再直接比较SLUB和SLAB的差异。
SLAB和SLUB分配器历来就需要解决kmem_cache结构先有鸡还是先有蛋的问题。开机第一个kmem_cache类型的数据是怎么创建的呢?答案是静态创建,然后迁移到slub分配器中。
mm/slub.c
3669 void __init kmem_cache_init(void)
3670 {
3671 static __initdata struct kmem_cache boot_kmem_cache,
3672 boot_kmem_cache_node;
3673
3674 if (debug_guardpage_minorder())
3675 slub_max_order = 0;
3676
3677 kmem_cache_node = &boot_kmem_cache_node;
3678 kmem_cache = &boot_kmem_cache;
3679
3680 create_boot_cache(kmem_cache_node, "kmem_cache_node",
3681 sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN);
3682
3683 register_hotmemory_notifier(&slab_memory_callback_nb);
3684
3685 /* Able to allocate the per node structures */
3686 slab_state = PARTIAL;
3687
3688 create_boot_cache(kmem_cache, "kmem_cache",
3689 offsetof(struct kmem_cache, node) +
3690 nr_node_ids * sizeof(struct kmem_cache_node *),
3691 SLAB_HWCACHE_ALIGN);
3692
3693 kmem_cache = bootstrap(&boot_kmem_cache);
... ...
3708
3709 pr_info("SLUB: HWalign=%d, Order=%d-%d, MinObjects=%d, CPUs=%d, Nodes=%d\n",
3710 cache_line_size(),
3711 slub_min_order, slub_max_order, slub_min_objects,
3712 nr_cpu_ids, nr_node_ids);
}
上面代码我们可以看到两点。
众所周知,内核既然创建了kmem_cache_node和kmem_cache两个类型的kmem_cache,那他们应该是马上就要被用到了,且以后也会被大量分配/回收的数据结构。先看第一个kmem_cache_node的创建过程,然后看第二个kmem_cache创建过程中如果从SLUB中获取一个kmem_cache_node的object。
static int init_kmem_cache_nodes(struct kmem_cache *s)
2972 {
2973 int node;
2974
2975 for_each_node_state(node, N_NORMAL_MEMORY) {
2976 struct kmem_cache_node *n;
2977
2978 if (slab_state == DOWN) {
2979 early_kmem_cache_node_alloc(node);
2980 continue;
2981 }
2982 n = kmem_cache_alloc_node(kmem_cache_node,
2983 GFP_KERNEL, node);
2984
2985 if (!n) {
2986 free_kmem_cache_nodes(s);
2987 return 0;
2988 }
2989
2990 s->node[node] = n;
2991 init_kmem_cache_node(n);
2992 }
2993 return 1;
2994 }
create函数最终会调用到这个函数,它的作用是初始化cache类型对应的kmem_cache管理结构。第一次调用这个函数时slab_state是DOWN,所以初始化时走的是early_kmem_cache_node_alloc()分支。这里提一下,当第一次调用完这个函数后,其他地方的后续代码会将slab_stat改成PARTIAL/UP等状态,所以后续比如创建第二个kmem_cache的cache以及创建kmalloc的cache时走的都是kmem_cache_alloc_node()分支。
early_kmem_cache_node_alloc()运行时还没有分配相关物理page给kmem_cache_node,所以这个函数需要额外做一些分配page,然后初始化成slab之类的工作。
当后续调用此函数走kmem_cache_alloc_node()分支时就可以直接取一个kmem_cache_node的object了。为什么呢?因为注意函数传入的第一个参数是kmem_cache_node,也就是明确说要去kmem_cache_node类型的kmem_cache中取一个object。
early_cache_alloc_node()的内容很简单,就是去buddy拿page过来,然后初始化成slab,并且将这个page加到kmem_cache_node的partial链表中。
下面具体分析kmem_cache_alloc_node,因为此函数会调用slub的核心分配函数slab_alloc,slab_alloc又是对slab_alloc_node的封装。
具体代码不展开了,贴整理出来的流程图。结合流程图和前沿链接的文章,应该能快速理解slub分配object的逻辑了。
kmalloc和task_struct等等cache的分配过程和基于slab是一样的。在初始化的时候都会创建固定大小的cache,代码调用kmalloc时候得先找到对应size的cache,然后从这个cache中分配object,SLUB分配object的逻辑就是前面核心分配函数的逻辑。
添加了部分log,kernel v4.1.50 + qemu + vexpress -a9的开机log如下:
[ 0.000000] kmalloc-192
[ 0.000000] kmalloc-64
[ 0.000000] kmalloc-128
[ 0.000000] kmalloc-256
[ 0.000000] kmalloc-512
[ 0.000000] kmalloc-1024
[ 0.000000] kmalloc-2048
[ 0.000000] kmalloc-4096
[ 0.000000] kmalloc-8192
可知当前平台支持kmalloc最小的单位是64Byte,最大是8192Byte(2个page),再大就需要直接通过buddy来获取了。
基本共识是SLUB基于SLAB管理的复杂性做了一些改进,优化了slab管理结构本身的冗余程度,减少了不必要的memory。至于性能,似乎没有达成一致。有说早期SLUB的性能比不上SLAB。网上还有很多SLUB SLAB的介绍可以参考。
- 4.x的内核默认配置的是SLUB。
- 《Linux内核设计与实现》的作者Robert Love提到任何情况下都应该使用SLUB,除非是嵌入式平台,并且应当在评估完性能之后再考虑使用SLUB还是SLOB。
- https://lkml.org/lkml/2016/8/23/411 这封mail thread中有过一些讨论。slab目前应该是出于freez的状态,git log查看mm/slab.c可以发现,最近的修改已经是2015年的了。
所以选SLUB是符合历史潮流的。