在Linux中,伙伴系统是以页为单位分配内存。但是现实中很多时候却以字节为单位,不然申请10Bytes内存还要给1页的话就太浪费了。slab分配器就是为小内存分配而生的。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。
做个小实验:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mm.h>
static struct kmem_cache* slub_test;
struct student{
int age;
int score;
};
static void mystruct_constructor(void *addr)
{
memset(addr, 0, sizeof(struct student));
}
struct student* peter;
int slub_test_create_kmem(void)
{
int ret = -1;
slub_test = kmem_cache_create("slub_test", sizeof(struct student), 0, 0, mystruct_constructor);
if(slub_test != NULL){
printk("slub_test create success!\n");
ret=0;
}
peter = kmem_cache_alloc(slub_test, GFP_KERNEL);
if(peter != NULL){
printk("alloc object success!\n");
ret = 0;
}
return ret;
}
static int __init slub_test_init(void)
{
int ret;
printk("slub_test kernel module init\n");
ret = slub_test_create_kmem();
return 0;
}
static void __exit slub_test_exit(void)
{
printk("slub_test kernel module exit\n");
kmem_cache_destroy(slub_test);
}
module_init(slub_test_init);
module_exit(slub_test_exit);
看下结果:
相关参数也可以通过如下路径查看:
总结:slab从buddy拿到一个order为0的内存,也就是一页,然后把这一页称为名为slub_test的slab,然后把这一页分成很多小的object的。当我们使用的时候就从slab中获取一个object使用,用完了在归还给slab管理即可。
他们之间的关系可以用一张图来描述:
kmem_cache_create
create_cache
kmem_cache_zalloc //分配kmem_cache结构
kmem_cache_alloc
slab_alloc
slab_alloc_node
__kmem_cache_create //初始化kmem_cache结构
kmem_cache_open
list_add //将创建的kmem_cache添加到全局链表slab_caches中,构成slab缓存池
kmem_cache_create 主要三步:
kmem_cache_alloc
slab_alloc
slab_alloc_node
if (kmem_cache_cpu里的freelist没有可用的object)
__slab_alloc
___slab_alloc
slub_percpu_partial //2.接着去 kmem_cache_cpu->partital链表中分配,如果此链表为null
new_slab_objects
get_partial //3.接着去 kmem_cache_node->partital链表分配,如果此链表为null
new_slab //4.这就需要重新分配一个slab了。
allocate_slab
else
get_freepointer_safe //1.先从 kmem_cache_cpu->freelist中分配,如果freelist为null
kmem_cache_alloc 主要四步:
虽然刚开始我们通过一个小实验了解了slab的用法,但是实际工作中什么时候会用到呢?其实我们在写驱动的时候经常用,那就是kmalloc。
kmalloc的内存分配就是基于slab分配器,系统在启动的时候会调用create_kmalloc_caches,来创建不同大小的kmem_cache,并将这些kmem_cache存储在kmalloc_caches全局变量中以备后面kmalloc的使用。各个kmem_cache的名字和大小如下所示:
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
{NULL, 0}, {"kmalloc-96", 96},
{"kmalloc-192", 192}, {"kmalloc-8", 8},
{"kmalloc-16", 16}, {"kmalloc-32", 32},
{"kmalloc-64", 64}, {"kmalloc-128", 128},
{"kmalloc-256", 256}, {"kmalloc-512", 512},
{"kmalloc-1024", 1024}, {"kmalloc-2048", 2048},
{"kmalloc-4096", 4096}, {"kmalloc-8192", 8192},
{"kmalloc-16384", 16384}, {"kmalloc-32768", 32768},
{"kmalloc-65536", 65536}, {"kmalloc-131072", 131072},
{"kmalloc-262144", 262144}, {"kmalloc-524288", 524288},
{"kmalloc-1048576", 1048576}, {"kmalloc-2097152", 2097152},
{"kmalloc-4194304", 4194304}, {"kmalloc-8388608", 8388608},
{"kmalloc-16777216", 16777216}, {"kmalloc-33554432", 33554432},
{"kmalloc-67108864", 67108864}
};
我们来看看kmalloc的实现:
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
if (!(flags & GFP_DMA)) {
//查找符合满足分配大小的最小kmem_cache
int index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
//将index作为下表从kmalloc_caches数组中找到符合的kmem_cache,并从slab缓存池中分配对象
return kmem_cache_alloc_trace(kmalloc_caches[index],
flags, size);
}
#endif
}
return __kmalloc(size, flags);
}
static __always_inline int kmalloc_index(size_t size)
{
if (!size)
return 0;
if (size <= KMALLOC_MIN_SIZE)
return KMALLOC_SHIFT_LOW;
if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
return 1;
if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
return 2;
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
if (size <= 4 * 1024 * 1024) return 22;
if (size <= 8 * 1024 * 1024) return 23;
if (size <= 16 * 1024 * 1024) return 24;
if (size <= 32 * 1024 * 1024) return 25;
if (size <= 64 * 1024 * 1024) return 26;
BUG();
/* Will never be reached. Needed because the compiler may complain */
return -1;
}
static __always_inline void *kmem_cache_alloc_trace(struct kmem_cache *s,
gfp_t flags, size_t size)
{
void *ret = kmem_cache_alloc(s, flags);
kasan_kmalloc(s, ret, size, flags);
return ret;
}