1. 程式人生 > >記憶體管理七 SLUB分配器管理記憶體

記憶體管理七 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
您的支援是對博主最大的鼓勵,感謝您的認真閱讀。
本文無所謂版權,歡迎轉載。