slub中的kmalloc和kfree学习笔记
完全转自: http://linux.chinaunix.net/bbs/thread-1110127-1-1.html
2.6.26中的内存管理大概分为3个层次 SLUB,伙伴系统和ZONE,其中SLUB在最高层,这里通过分析kmalloc和kfree来分析SLUB的模型,在内存管理中还有NUMA系统,但是NUMA不是必须得,所以以下笔记建立在无SMP和不使用NUMA的环境下,并且不运行DEBUG设置
SLUB主要对1页以下的内存进行管理,将1页内存分成相同大小的块,SLUB将这些块称为object,内核进行内存申请时则分配1个块,也就是1个object
在x86下的32位处理器中,SLUB由13个缓冲结构组成,每个缓冲结构管理大小不同的object,其中0号归NUMA使用,其它12个按顺序分别为96,192,8,16,32,64,128,256,512,1024,2048,4096,如下图所示:
举例来说,第5个缓冲结构管理object大小为16的页面,对于该缓冲来说,将1页内存按16的大小分成了256项,当内核申请1个大小为9-16大小的内存时,SLUB就根据第5个缓冲结构中的空闲object指针freelist取出1个object交给内核
这里可以发现,申请大小为9时,返回16的大小,申请大小为15时,也返回16的大小,大家会认为如果申请的内存都在9到10大小左右徘徊的时候就会浪费大概50%的内存空间,对的,所以学习SLUB就更有必要了嘛,如果都在9-10大小的话就自己更改SLUB缓冲的结构,设置1个9-10大小的缓冲区,专门负责这些内存申请.避免浪费
对每页内存进行分块,虽然在一定程度上浪费了内存,但是方便了内存的申请与回收,提高了效率,下面就对SLUB的这种管理进行分析
首先是SLUB的缓冲结构kmalloc_caches[]数组的初始化,该初始化在kmem_cache_init中进行
kmem_cache_init在/mm/slub.c中,代码如下:
void __init kmem_cache_init(void) { int i; int caches = 0; init_alloc_cpu(); #ifdef CONFIG_NUMA create_kmalloc_cache(&kmalloc_caches[0], "kmem_cache_node", sizeof(struct kmem_cache_node), GFP_KERNEL); kmalloc_caches[0].refcount = -1; caches++; hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI); #endif slab_state = PARTIAL; //如果kmalloc的最小object小于64 //则初始化1号和2号kmalloc_caches的大小为96和192 if (KMALLOC_MIN_SIZE <= 64) { create_kmalloc_cache(&kmalloc_caches[1], "kmalloc-96", 96, GFP_KERNEL); caches++; create_kmalloc_cache(&kmalloc_caches[2], "kmalloc-192", 192, GFP_KERNEL); caches++; } //按照kmalloc的最小object初始化kmalloc_caches for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++) { create_kmalloc_cache(&kmalloc_caches[i], "kmalloc", 1 << i, GFP_KERNEL); caches++; } BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 || (KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1))); //按照kmalloc的最小object重新设置size_index for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) size_index[(i - 1) / 8] = KMALLOC_SHIFT_LOW; if (KMALLOC_MIN_SIZE == 128) { for (i = 128 + 8; i <= 192; i += 8) size_index[(i - 1) / 8] = 8; } slab_state = UP; //按照kmalloc的最小object重新设置kmalloc_caches的名字 for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++) kmalloc_caches[i]. name = kasprintf(GFP_KERNEL, "kmalloc-%d", 1 << i); #ifdef CONFIG_SMP register_cpu_notifier(&slab_notifier); kmem_size = offsetof(struct kmem_cache, cpu_slab) + nr_cpu_ids * sizeof(struct kmem_cache_cpu *); #else kmem_size = sizeof(struct kmem_cache); #endif printk(KERN_INFO "SLUB: Genslabs=%d, HWalign=%d, Order=%d-%d, MinObjects=%d," " CPUs=%d, Nodes=%d/n", caches, cache_line_size(), slub_min_order, slub_max_order, slub_min_objects, nr_cpu_ids, nr_node_ids); } |
由于不使用NUMA系统,所以这里不会执行#ifdef CONFIG_NUMA中的代码,也就不会初始化0号缓冲
所有缓冲结构的初始化都是由create_kmalloc_cache负责
create_kmalloc_cache在/mm/slub.c中,代码如下:
static struct kmem_cache *create_kmalloc_cache(struct kmem_cache *s, const char *name, int size, gfp_t gfp_flags) { unsigned int flags = 0; //检测是否为DMA缓冲结构 if (gfp_flags & SLUB_DMA) //是则加上DMA标志 flags = SLAB_CACHE_DMA; down_write(&slub_lock); //分配一个缓冲 if (!kmem_cache_open(s, gfp_flags, name, size, ARCH_KMALLOC_MINALIGN,flags, NULL)) goto panic; //将该缓冲挂载到slab_caches链表中 list_add(&s->list, &slab_caches); up_write(&slub_lock); if (sysfs_slab_add(s)) goto panic; return s; panic: panic("Creation of kmalloc slab %s size=%d failed./n", name, size); } |
主要的初始化在kmem_cache_open中进行
kmem_cache_open在/mm/slub.c中,代码如下:
static int kmem_cache_open(struct kmem_cache *s, gfp_t gfpflags, const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(struct kmem_cache *, void *)) { //初始化kmem缓冲,将内容全部清零 memset(s, 0, kmem_size); //设置缓冲的名字 s->name = name; //设置缓冲的object初始化函数 s->ctor = ctor; //设置缓冲的object大小 s->objsize = size; //设置缓冲的对齐 s->align = align; //设置缓冲的标志 s->flags = kmem_cache_flags(size, flags, name, ctor); //根据object的大小计算对应的object数目 if (!calculate_sizes(s, -1)) goto error; s->refcount = 1; #ifdef CONFIG_NUMA s->remote_node_defrag_ratio = 100; #endif //初始化邻居页面链表 if (!init_kmem_cache_nodes(s, gfpflags & ~SLUB_DMA)) goto error; //初始化CPU的私有kmem缓冲 if (alloc_kmem_cache_cpus(s, gfpflags & ~SLUB_DMA)) return 1; free_kmem_cache_nodes(s); error: if (flags & SLAB_PANIC) panic("Cannot create slab %s size=%lu realsize=%u " "order=%u offset=%u flags=%lx/n", s->name, (unsigned long)size, s->size, oo_order(s->oo), s->offset, flags); return 0; } |
calculate_sizes负责计算object的大小,就是对kmem_cache结构中oo,max,min成员的赋值,以及对object大小进行边界和字对齐,但是对边界和字对齐还不熟悉,所以我就不分析了 = 3=
接下来是init_kmem_cache_nodes, init_kmem_cache_nodes负责对邻居页面链表进行初始化
init_kmem_cache_nodes在/mm/slub.c中,由于这里不使用NUMA系统,所以代码如下:
static int init_kmem_cache_nodes(struct kmem_cache *s, gfp_t gfpflags) { init_kmem_cache_node(&s->local_node); return 1; } |
init_kmem_cache_node在mm/slub.c中,代码如下:
static void init_kmem_cache_node(struct kmem_cache_node *n) { n->nr_partial = 0; spin_lock_init(&n->list_lock); INIT_LIST_HEAD(&n->partial); #ifdef CONFIG_SLUB_DEBUG atomic_long_set(&n->nr_slabs, 0); INIT_LIST_HEAD(&n->full); #endif } |
主要进行了一下初始化工作
回到kmem_cache_open中,现在到alloc_kmem_cache_cpus, alloc_kmem_cache_cpus负责CPU私有缓冲的初始化工作
alloc_kmem_cache_cpus在mm/slub.c中,由于不使用SMP,所以代码如下:
static inline int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags) init_kmem_cache_cpu在mm/slub.c中,代码如下
初始化完成后, kmem_cache_open和create_kmalloc_cache也执行完了,返回到kmem_cache_init中,接下来kmem_cache_init主要执行缓冲名字的设置工作 下图是第6个缓冲结构,也就是object大小为32的缓冲初始化后的结构图 ![]() kmem_cache_init执行完成后,SLUB的初始化就完成了,就接下来我们就能使用kmalloc进行内存的分配了 假设kmalloc申请的大小为32,标志为GFP_KKERNEL,GFP_KERNEL是标志_GFP_WAIT , _GFP_IO 和 _GFP_FS的集合,也就是kmalloc(32,GFP_KERNEL) 下面就进入到kmalloc的分析中 kmalloc在mmslub.c中,代码如下
__builtin_constant_p检测参数是变量还是常量,举个例子说kmalloc(i,GFP_KERNEL)就是变量,kmalloc(32,GFP_KERNEL)就是常量 这里先看常量,进入if中,这里先说一下kmalloc_large, kmalloc_large负责超过1页内存的申请,超过1页的内存分配由伙伴系统进行,不由SLUB进行. 接下来到if (!(flags & SLUB_DMA)),这里我们申请的内存标志为GFP_KERNEL,没有DMA标志,所以进入到if中 首先根据申请的大小选取对应的缓冲序号,进入到kmalloc_slab中 kmalloc_slab在include linuxslub_def.h中,代码如下
继续进入到kmalloc_index kmalloc_index在include /linux/slub_def.h中,代码如下:
得到缓冲结构后,就来到了kmem_cache_alloc中 kmem_cache_alloc在mm/slub.c中
简单的调用, __builtin_return_address产生的值用于DEBUG,这里我们并不会使用到,继续来到slab_alloc中 slab_alloc在mm/slub.c中,代码如下:
get_cpu_slab负责取得CPU的私有kmem缓冲 |