Linux内存管理之SLUB和SLAB之间的区别

前言

前一篇讲了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 类型的结构,kmem_cache_node和kmem_cache。
  • bootstrap函数实现了从静态分配到SLUB分配器的迁移

众所周知,内核既然创建了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。

SLUB核心分配函数

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的逻辑了。
Linux内存管理之SLUB和SLAB之间的区别_第1张图片

基于SLUB的kmalloc

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

基本共识是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是符合历史潮流的。

你可能感兴趣的:(Linux内核)