内存管理七 SLUB分配器管理内存

一、概序

  linux内存管理的基础是:伙伴系统(buddy system),但伙伴系统是以页为单位(4kB)管理和分配内存。现实

的需求是以字节为单位,这样基于Buddy系统分配最小的一个page会严重的浪费内存。slab分配器就是为了解决此问

题而出现,专为小内存分配而生。slab分配器分配内存以Byte为单位。但是slab分配器是基于伙伴系统分配的大内存

进一步细分成小内存分配,是在buddy系统上封装了一层算法实现此功能,后面会介绍slab分配的原理及方法。

  另外当前都使用的SLUB分配器,SLUB分配器是基于SLAB分配做的优化,使得可以快速地进行对象的分配和回

收并减少内存碎片,发明SLUB分配器的主要目的就是减少slab缓冲区的个数,让更多的空闲内存得到使用。

二、相关结构体

  slub分配器来说,就是将这段连续内存平均分成若干大小相等的object(对象)进行管理。 slub把内存分组管理,

每个组分别包含2^3、2^4、...2^11个字节,每个分组使用一个struct kmem_cache的结构体来描叙。

/*
 * Slab cache management.
 */
struct kmem_cache {
	struct kmem_cache_cpu __percpu *cpu_slab;

	int size;		/* The size of an object including meta data */
	int object_size;	/* The size of an object without meta data */
	int offset;		/* Free pointer offset. */

	struct list_head list;	/* List of slab caches */

	struct kmem_cache_node *node[MAX_NUMNODES];
};
  • cpu_slab:一个per cpu变量,对于每个cpu来说,相当于一个本地内存缓存池;
  • size:分配给对象object的内存大小(可能大于对象的实际大小);
  • object_size:实际的object size,就是创建kmem_cache时候传递进来的参数;
  • offset:存放空闲对象指针的位移;
  • list:系统有一个slab_caches链表,所有的slab都会挂入此链表;
  • node:为每个节点创建的 slab 信息的数据结构,每个node都有一个struct kmem_cache_node数据结构。
/*
 * The slab lists for all objects.
 */
struct kmem_cache_node {
	spinlock_t list_lock;

#ifdef CONFIG_SLUB
	unsigned long nr_partial;
	struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
	atomic_long_t nr_slabs;
	atomic_long_t total_objects;
	struct list_head full;
#endif
#endif
};
  • list_lock:自旋锁,保护数据;
  • nr_partial:slab节点中slab的数量;
  • partial:slab节点的slab partial链表,保存部分使用的链表。
struct kmem_cache_cpu {
	void **freelist;	/* Pointer to next available object */
	unsigned long tid;	/* Globally unique transaction id */
	struct page *page;	/* The slab from which we are allocating */
	struct page *partial;	/* Partially allocated frozen slabs */
#ifdef CONFIG_SLUB_STATS
	unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};
  • freelist:指向下一个可用的object;
  • page:slab内存的page指针;
  • partial:本地slab partial链表,主要是一些部分使用object的slab。

  struct kmem_cache *kmalloc_caches[12];

  关于上面三个链表的关系通俗的理解可以参考文章slub算法,每个数组kmalloc_caches元素对应一种大小的内存,

可以把一个kmem_cache结构体看做是一个特定大小内存的零售商,整个slub系统中共有12个这样的零售商,每个

“零售商”只“零售”特定大小的内存,例如:有的“零售商”只"零售"8Byte大小的内存,有的只”零售“16Byte大小的内存。

每个零售商(kmem_cache)有两个“部门”,一个是“仓库”:kmem_cache_node,一个“营业厅”:kmem_cache_cpu。

“营业厅”里只保留一个slab,只有在营业厅(kmem_cache_cpu)中没有空闲内存的情况下才会从仓库中换出其他的slab。

三、SLUB分配及释放接口

1、创建kmem_cache:

struct kmem_cache *kmem_cache_create(const char *name, 
                                     size_t size, size_t align,
                                     unsigned long flags, 
                                     void (*ctor)(void *))
  • name:kmem_cache的名称;
  • size :创建的slab管理对象的大小;
  •  align:slab分配器分配内存的对齐字节数(以align字节对齐);
  • flags:分配内存掩码;
  • ctor :分配对象的构造回调函数。

2、kmem_cache_destroy和kmem_cache_create相反,销毁创建的对应的slub的kmem_cache结构。

3、分配object的对象kmem_cache_alloc

void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
  • struct kmem_cache *s:从指定的缓冲池s中分配对象;
  • gfpflags:分配掩码;

4、kmem_cache_free是kmem_cache_alloc的反操作。

5、例程:

void slab_test(void)
{
    //create 16byte kmem_cache kmem_cache_16
    struct kmem_cache *kmem_cache_16 = kmem_cache_create("kmem_cache_16", 16, 8, ARCH_KMALLOC_FLAGS, NULL);

    //alloc buf points of 16 bytes of memory
    char *buf = kmeme_cache_alloc(kmem_cache_16, GFP_KERNEL);
 
    //release the memory after use
    kmem_cache_free(kmem_cache_16, buf);
 
    //release kmem_cache
    kmem_cache_destroy(kmem_cache_16);
}


四、SLUB分配原理

  slub的分配原理可以从slub分配器的分配函数看起,kmem_cache_alloc -> slab_alloc -> slab_alloc_node -> 

__slab_alloc -> ___slab_alloc ,其中整个分配的流程可以用如下图清晰的说明。首先从cpu 本地缓存池分配,如

果freelist不存在,就会转向cpu partial分配,如果cpu partial也没有可用对象,继续查看node partial,如果很不幸

也不没有可用对象的话,就只能从伙伴系统分配一个slab:

2.png

五、Kmalloc:

  内核中使用的kmalloc函数也是基于slub分配器做的封装,按照内存块的2^order大小来创建slab描叙符。分配

内存的大小可以为16B、32B、64B、128B......32Mb,其对应的分配接口为:kmalloc-16、kmalloc-32、kmalloc-64。

在系统启动初期会调用create_kmalloc_caches ()创建多个管理不同大小对象的slab描叙符kmem_cache(包含16B、

32B、64B、128B......32Mb大小)。

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);
		if (!(flags & GFP_DMA)) {
			int index = kmalloc_index(size);

			if (!index)
				return ZERO_SIZE_PTR;

			return kmem_cache_alloc_trace(kmalloc_caches[index],
					flags, size);
		}
	}
	return __kmalloc(size, flags);
}

  根据传入的对应的size选择对应的kmem_cahe,系统只能分配2^order大小的slab内存,如通过kmalloc(17,

GFP_KERNEL)申请内存,系统会从名称“kmalloc-32”管理的slab缓存池中分配一个对象。即使浪费了15Byte:

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 <=  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;
}

参考博文:

http://www.wowotech.net/memory_management/426.html

https://www.ibm.com/developerworks/cn/linux/l-cn-slub/

https://blog.csdn.net/lukuen/article/details/6935068

作者:frank_zyp
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。

猜你喜欢

转载自blog.csdn.net/frank_zyp/article/details/83013811