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的逻辑了。
这里写图片描述

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

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内存管理中的页面分配器是负责分配和管理物理页面(Page)的一种机制。在内核中,物理页面是以固定大小的块进行管理的,通常被称为页面帧(Page Frame)。页面分配器的主要任务是从可用的物理内存中分配页面帧,并在需要时释放这些页面帧。 Linux中有多种页面分配器,其中最常用的是伙伴系统(Buddy System)。伙伴系统将可用的物理内存划分为不同大小的块,每个块都是2的幂次方大小。当需要分配一块n个页面帧大小的内存时,伙伴系统会找到一个大小为2的幂次方,并且大于等于n的最小块。如果找到的块大于n,则将该块分裂成两个小块,其中一个块将被分配,另一个块将被留作备用。如果找到的块恰好是n,则将该块分配出去。如果没有合适的块可用,则会尝试从内存中释放一些页面帧来获得可用的内存。 伙伴系统的优点是效率高,分配和释放内存的速度都很快。它还可以避免内存碎片的问题,因为它只分配大小为2的幂次方的块,这些块可以非常有效地组合在一起,而不会留下任何碎片。但是,伙伴系统的缺点是它会浪费一些内存。当一个块被分割成两个小块时,其中一个块可能永远不会被使用,从而浪费了一些内存。 除了伙伴系统之外,Linux还提供了其他的页面分配器,如SLABSLUB。这些页面分配器适用于不同的场景,具有不同的优缺点。在实际应用中,应该根据具体的需求选择适合的页面分配器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值