linux内核内存管理slub

一、概述

linux内存管理核心是伙伴系统,slab,slub,slob是基于伙伴系统之上提供api,用于内核内存分配释放管理,适用于小内存(小于1页)分配与释放,当然大于1页,也是可以的.小于一页的,我们也可以直接用伙伴系统api申请内存和释放,但伙伴系统最小单位是页,如果我们只需要100byte,伙伴系统申请内存最小1页(一般情况1页是4k, 具体看系统PAGE_SHIFT定义),显然就很浪费.

二、slub核心原理

slub是从伙伴系统中分配连续一块内存页作为缓存池,再将其分成大小相等,多块小内存块,当申请内存时,返回其中一小块(返回小块内存起始地址.如下图),假设分成9小块内存(简单的数值表示地址):
在这里插入图片描述
每块小内存的首地址,存放下一小块内存起始地址,最后一小块内存首地址存放null.
缓存池(如上图,9个地址连续小内存块,可视为缓存池),从开始分配到小块内存分配完的过程:
假设指针p=0,表示指向第0小块内存.首次分配内存时,首先addr=p, p=*p(从第0小块内存首地址中获取下一小块内存起始地址,并写入p指针中)之后p=1指向第1小块内存起始地址,addr(指向第0小块内存起始地址)返回给申请分配内存的程序;第二次分配内存时,首先addr=p,p=*p,此后p=2,以此类推,直到最后,将第8小块内存地址分配完后,若再次发起内存申请时,发现p=null,表示内存分配完了,此时,会再次向伙伴系统申请分配连续的,一块大内存(缓存池).

一块缓存池,释放过程:
释放内存时,假设有个指针pa, 首次释放的内存,是第5小块内存,pa=5,*pa=null(第5小块内存首地址存放null), 然后第8小块内存被释放,这时候pa=8,*pa=5(第8小块内存首地址存放5),第5小块内存首地址仍然存放null,, 以此类推.

假如释放顺序是5,8,2,0,6,4,3,1,7释放完后,pa=7,内存情况如下:
在这里插入图片描述
释放完或者只释放部分,随时都可以加入到分配中去.

slub就是负责:缓存池之间的切换,以及小内存块分配与释放.

那么,小内存块(如上面9小块)大小,块数是怎么计算,缓存池大小又是怎么计算?

小内存块大小计算:

假如,申请x_size大小的内存,那么:

n = fls(x_size - 1);
j = 1 << n;
or = j < x_size ? 1 << n+1 : j;

or就是小块内存大小
fls()返回最高位为1的位置,起始是从1开始,返回0表示没有找到为1的位置
小块内存大小,也可以直接调用include/linux/slab.h里面定义的kmalloc_index()得到:

a = kmalloc_index(x_size);
or = a < 0 ? 0 : 1 << a;

由此,可看出,最终分配的小内存块大小,不一定是申请时指定的大小,而是2的n次方,不足往大的计算,如申请5字节,最终计算下来,会分配8字节返回.在申请内存时,大小按照2的n次方申请最佳,否则会出现浪费.如我们申请5字节,分配8字节,多余的3字节,申请者,不知道,就不会去使用这3字节,出现浪费.

缓存池大小计算:


int rem = 0;
int order = 0;
int max_order;
unsigned long slab_size;

//2的order次幂,就是要向伙伴系统申请的,连续大块内存,单位页
for(order = 0; order < max_order; order++)//max_order最大限制
{
	slab_size = PAGE_SIZE << order;//将order,转化为字节(byte)大小
	//reserved,一般为0
	//x_size小块内存大小
	rem = (slab_size - reserved) % x_size;//碎片,残留rem字节大小内存,是无法分配的
	if (rem <= slab_size / fract_leftover)//fract_leftover会分别用16,8,4试探,看是否满足条件
		break;//表示成功
}

printk("order:%d\n",order)

上面的代码,是个人理解后的简单描述,实现在mm/slub.c中calculate_order()函数.
2^order(2的order次幂)个页,就是缓存池大小.
简而言之,以2^order个页,循环试探,找到满足,对碎片要求的值order.

缓存池中小内存块,块数计算:
slab_size = PAGE_SIZE << order;
num = slab_size / or;
num就是小内存块,块数.

slub最大只能申请分配2页内存空间(小内存块),在include/linux/slab.h中定义:
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)
其中的PAGE_SHIFT,需要看具体系统配置,一般会定义在arch/xxx/include/asm/page.h里面:
#define PAGE_SHIFT 12

slub最小能分配内存空间(小内存块),由宏KMALLOC_SHIFT_LOW定义,在include/linux/slab.h,KMALLOC_SHIFT_LOW会受平台定义的ARCH_DMA_MINALIGN影响,如果平台没定义ARCH_DMA_MINALIGN,slub默认:
#define KMALLOC_SHIFT_LOW 3
即,最小可以申请8个字节(1 << 3),假如申请3个字节,也会分配8字节.

三、slub code实现

为了便于描述,如下定义:

同类缓存池:struct kmem_cache *s;
小缓存池:struct page *page;
当前小缓存池:s->cpu_slab->page;
当前小内存块链:s->cpu_slab->freelist
一级(小缓存池)回收链表:s->cpu_slab->partial
二级(小缓存池)回收链表:s->node[x]->partial

小内存块:"slub核心原理"描述的,分成多个内存块,其中的一块,就叫做小内存块,即每次向slub申请分配,获取到的内存,就是一块小内存块,小内存块大小由struct kmem_cache成员size指定.
同类缓存池:一个struct kmem_cache实例化对象,管理着同一类缓存池(一个或多个缓存池),这里说的同一类,指相同大小小内存块,即可以叫做相同size小内存块,struct kmem_cache成员size(object_size与size相等)就是小内存块的大小.例如:size=1024的小内存块,与size=2048的小内存块,就属于不同类.(个人觉得,struct kmem_cache成员size,命名为size_type,或许更合适)
小缓存池:slub每次向伙伴系统申请分配内存页(单个或多个连续页),就可以叫做小缓存池.向伙伴系统分配的页大小由s->oo决定.如上"slub核心原理"提到的0到8的九个小块内存,组成一个小缓存池.
大缓存池:为方便描述和理解,同类缓存池取个别名,叫做大缓存池,里面有多个小缓存池.

1、同类缓存池初始化

同类缓存池结构初始化后,形成的大致结构图:
在这里插入图片描述                图一
slab_caches是链表头,为了便于描述,我们取名为同类缓存池链表,或者大缓存池链表,定义在mm/slab_common.c中:
LIST_HEAD(slab_caches);
kmem_cache是指向struct kmem_cache的一个实体对象,定义在mm/slab_common.c中:
struct kmem_cache *kmem_cache;
kmem_cache_node是指向struct kmem_cache_node的一个实体对象,定义在mm/slub.c中;
static struct kmem_cache *kmem_cache_node;

kmem_cache,kmem_cache_node,kmalloc_caches[0], kmalloc_caches[1], …kmalloc_caches[i]都是struct kmem_cache实体,以slab_caches为列表头,通过链表(struct list_head)形式连接起来.

s0,s1,s2…sn分别代表不同size的,同类缓存池管理结构(即struct kmem_cache一个实体对象)

memory0, memory1, memory2…memoryN代表缓存池,即实际内存空间,图一方框大小并不代表缓存池空间大小

kmem_cache, s0代表同一个struc kmem_cache实体,即同类缓存池管理结构,kmem_cache和s0不分彼此,图一只是为了更好的描述.有意思的地方,mermory0用来存放小内存块s0, s1, s2…sn;而s0用来管理memory0缓存池里面其他小内存块分配释放.
依次,有如下关系:
kmem_cache_node对应s1,管理memory1
kmalloc_caches[0]对应s2,管理memory2
kmalloc_caches[1]对应s3,管理memory3

如下:
kmalloc_caches[6]管理所有大小为64byte的内存块(本文称为小内存块)分配释放.
kmalloc_caches[7]管理所有大小为128byte的内存块分配释放.
kmalloc_caches[8]管理所有大小为256byte的内存块分配释放.

在分析代码是如何描述图一结构前,先了解下同类缓存池struct kmem_cache结构体(定义在include/linux/slub_def.h里面):

struct kmem_cache {
	//cpu_slab管理当前小缓存池和一级回收链表
	struct kmem_cache_cpu __percpu *cpu_slab;
	/* Used for retriving partial slabs etc */
	unsigned long flags;
	//二级回收链表上小缓存池数量超过min_partial时,
	//将会把二级回收链表超过的部分,且inuse为0时,释放回伙伴系统
	//unfreeze_partials()函数做的处理
	unsigned long min_partial;
	//size同类缓存池关键,小内存块空间大于等于size
	int size;		/* The size of an object including meta data */
	//size == object_size
	int object_size;	/* The size of an object without meta data */
	int offset;		/* Free pointer offset. */
	//一级回收链表上小内存块数量超过cpu_partial时,
	//将会把一级回收链表上所有小缓存池移动到二级回收链表上
	//unfreeze_partials()函数会做这样的处理
	int cpu_partial;	/* Number of per cpu partial objects to keep around */
	//oo低16位存放小缓存池小内存块数量,
	//oo高16位,存放小缓存池空间大小(高16位是2的幂指数,2^(oo>>16)得到的值,就是小缓存池空间大小)
	struct kmem_cache_order_objects oo;

	/* Allocation and freeing of slabs */
	//max是oo>>16的上限值,slub向伙伴系统申请内存页时,不得超过这个值
	struct kmem_cache_order_objects max;
	// oo高16位的最小值,当缓存池向伙伴系统申请分配页时,按照oo申请不成功时,
	//会降低为min的值,再次向伙伴系统申请页
	struct kmem_cache_order_objects min;
	gfp_t allocflags;	/* gfp flags to use on each alloc */
	int refcount;		/* Refcount for slab cache destroy */
	void (*ctor)(void *);
	//inuse == size
	int inuse;		/* Offset to metadata */
	int align;		/* Alignment */
	//reserved一般为0
	int reserved;		/* Reserved bytes at the end of slabs */
	//同类缓存池名字,便于管理和查询,debug
	const char *name;	/* Name (only for display!) */
	//所有同类缓存池都是通过list连接到slab_caches(同类缓存池链表头)
	struct list_head list;	/* List of slab caches */
#ifdef CONFIG_SYSFS
	struct kobject kobj;	/* For sysfs */
#endif
#ifdef CONFIG_MEMCG_KMEM
	struct memcg_cache_params *memcg_params;
	int max_attr_size; /* for propagation, maximum size of a stored attr */
#endif

#ifdef CONFIG_NUMA
	/*
	 * Defragmentation by allocating from a remote node.
	 */
	int remote_node_defrag_ratio;
#endif
	//node管理二级回收链表
	//多核CPU,MAX_NUMNODES代表CPU核数量,每个CPU核都有自己独立的一个node
	struct kmem_cache_node *node[MAX_NUMNODES];
};

struct kmem_cache_cpu {
	//freelist指向当前小内存块链头
	void **freelist;	/* Pointer to next available object */
	unsigned long tid;	/* Globally unique transaction id */
	//page指向当前小缓存池
	struct page *page;	/* The slab from which we are allocating */
	//partial指向一级回收链表
	struct page *partial;	/* Partially allocated frozen slabs */
#ifdef CONFIG_SLUB_STATS
	unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

//min的作用(在mm/slub.c中函数allocate_slab())如下:
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	...
	...
	...
	page = alloc_slab_page(alloc_gfp, node, oo);
	if (unlikely(!page)) {//按照oo申请失败,降低为min再次试图向伙伴系统申请
		oo = s->min;
		/*
		 * Allocation may have failed due to fragmentation.
		 * Try a lower order alloc if possible
		 */
		page = alloc_slab_page(flags, node, oo);

		if (page)
			stat(s, ORDER_FALLBACK);
	}
	...
	...
	...

slub初始化
函数调用情况:
start_kernel() --> mm_init() --> kmem_cache_init()
start_kernel(), mm_init()都定义在init/main.c中
kmem_cache_init()定义在mm/slub.c中,函数如下:

void __init kmem_cache_init(void)
{
	//boot_kmem_cache为启动图一里面kmem_cache(s0)铺路或者引路
	//boot_kmem_cache_node为启动kmem_cache_node(s1)铺路或引路
	static __initdata struct kmem_cache boot_kmem_cache,
		boot_kmem_cache_node;

	if (debug_guardpage_minorder())
		slub_max_order = 0;

	kmem_cache_node = &boot_kmem_cache_node;
	kmem_cache = &boot_kmem_cache;

	//初始化boot_kmem_cache_node
	//第二个参数,作为缓存池管理结构名字boot_kmem_cache_node.name
	//第三个参数,作为缓存池管理结构boot_kmem_cache_node大小,即小内存块大小
	//这个函数,还会计算小内存块数量	
	create_boot_cache(kmem_cache_node, "kmem_cache_node",
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN);

	register_hotmemory_notifier(&slab_memory_callback_nb);

	/* Able to allocate the per node structures */
	slab_state = PARTIAL;

	//初始化缓存池管理结构boot_kmem_cache
	//第二个参数,作为缓存池管理结构名字boot_kmem_cache.name
	//第三个参数,作为缓存池管理结构boot_kmem_cache大小,即小内存块空间大小
	//这个函数,还会计算出小内存块数量	
	create_boot_cache(kmem_cache, "kmem_cache",
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
		       SLAB_HWCACHE_ALIGN);

	//将从memory0中分配s0,然后将boot_kmem_cache内容拷贝到s0	
	//当bootstrap()返回后,kmem_cache将不再指向boot_kmem_cache,
	//而是指向从伙伴系统分配来的内存空间s0,
	//之后kmem_cache(s0)管理的缓存池memory0,将用存放所有同类缓存池管理结构实体的,
	//(需要注意,同类缓存池管理结构struct kmem_cache实体,本身就是需要空间存放,
	//所有管理结构实体都存放在memory0中,而s0用来管理memory0分配释放)
	kmem_cache = bootstrap(&boot_kmem_cache);

	/*
	 * Allocate kmem_cache_node properly from the kmem_cache slab.
	 * kmem_cache_node is separately allocated so no need to
	 * update any list pointers.
	 */
	//从memory0中分配出s1小内存块,然后把boot_kmem_cache_node拷贝到s1里面
	//bootstrap()返回后,kmem_cache_node指向s1
	kmem_cache_node = bootstrap(&boot_kmem_cache_node);

	/* Now we can use the kmem_cache to allocate kmalloc slabs */
	//主要初始化kmalloc_caches[n]
	//后期分配内存,通过申请size,调用函数kmalloc_index()或者kmalloc_slab()
	//计算查找出n,并得出kmalloc_caches[x]同类缓存池管理结构实体
	//其中n,表示将分配出大小为1<<n的内存块空间(小内存块),即2的n次方
	create_kmalloc_caches(0);

#ifdef CONFIG_SMP
	register_cpu_notifier(&slab_notifier);
#endif

	printk(KERN_INFO
		"SLUB: HWalign=%d, Order=%d-%d, MinObjects=%d,"
		" CPUs=%d, Nodes=%d\n",
		cache_line_size(),
		slub_min_order, slub_max_order, slub_min_objects,
		nr_cpu_ids, nr_node_ids);
}

void __init create_boot_cache(struct kmem_cache *s, const char *name, size_t size,
		unsigned long flags)
{
	int err;

	s->name = name;//同类缓存池管理结构名字
	s->size = s->object_size = size;//小内存块空间大小,这很关键哦
	//内存对齐
	s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);
	//同类缓存池管理结构主要初始化
	err = __kmem_cache_create(s, flags);

	if (err)
		panic("Creation of kmalloc slab %s size=%zu failed. Reason %d\n",
					name, size, err);

	s->refcount = -1;	/* Exempt from merging for now */
}

int __kmem_cache_create(struct kmem_cache *s, unsigned long flags)
{
	int err;

	err = kmem_cache_open(s, flags);
	if (err)
		return err;
	...
}

static int kmem_cache_open(struct kmem_cache *s, unsigned long flags)
{
	s->flags = kmem_cache_flags(s->size, flags, s->name, s->ctor);
	s->reserved = 0;

	if (need_reserve_slab_rcu && (s->flags & SLAB_DESTROY_BY_RCU))
		s->reserved = sizeof(struct rcu_head);

	//小缓存池里面小内存块数量计算,计算方法,可参考以上"slub核心原理"
	if (!calculate_sizes(s, -1))
		goto error;
	...	
	//二级回收链表上,小缓存池数量min_partial上限初始化
	set_min_partial(s, ilog2(s->size) / 2);
	...	
	//一级回收链表上小内存块数量上限cpu_partial初始化
	//超过cpu_partial,将会把一级回收链表上所有小缓存池移动到二级回收链表上
	if (kmem_cache_debug(s))
		s->cpu_partial = 0;
	else if (s->size >= PAGE_SIZE)
		s->cpu_partial = 2;
	else if (s->size >= 1024)
		s->cpu_partial = 6;
	else if (s->size >= 256)
		s->cpu_partial = 13;
	else
		s->cpu_partial = 30;
	...
	//二级回链表初始化
	if (!init_kmem_cache_nodes(s))
		goto error;

	//当前缓存池,一级回收链表初始化
	if (alloc_kmem_cache_cpus(s))
		return 0;
	//当前缓存池,一级回收链表,二级回收链表初始化失败时,将会释放掉二级回收链表
	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)s->size, s->size, oo_order(s->oo),
			s->offset, flags);
	return -EINVAL;
}

static int calculate_sizes(struct kmem_cache *s, int forced_order)
{
	unsigned long flags = s->flags;
	unsigned long size = s->object_size;
	int orde
	...
	size = ALIGN(size, sizeof(void *));
	...
	s->inuse = size;
	...
	size = ALIGN(size, s->align);
	s->size = size;
	if (forced_order >= 0)
		order = forced_order;
	else
		//calculate_order()计算方式,见"slub核心原理"描述
		order = calculate_order(size, s->reserved);
	
	if (order < 0)
		return 0;
	...
	//oo高16位存放order,其中order是向伙伴系统申请内存空间的大小,2的order次方页
	//oo低16位存放,小内存块数量,即小缓存池里面有多少个小内存块(2的order次方除以size)
	s->oo = oo_make(order, size, s->reserved);	
	s->min = oo_make(get_order(size), size, s->reserved);
	if (oo_objects(s->oo) > oo_objects(s->max))
		s->max = s->oo;

	return !!oo_objects(s->oo);static int init_kmem_cache_nodes(struct kmem_cache *s)
{
	int node;
	
	//node cpu核编号(多核cpu)
	for_each_node_state(node, N_NORMAL_MEMORY) {
		struct kmem_cache_node *n;
		...
		//从memory1中分配一个二级回收链表struct kmem_cache_node实体内存空间
		n = kmem_cache_alloc_node(kmem_cache_node,
						GFP_KERNEL, node);

		if (!n) {//分配失败,释放二级回收链表
			free_kmem_cache_nodes(s);
			return 0;
		}

		s->node[node] = n;//二级回收链表指针,指向实体struct kmem_cache_node
		init_kmem_cache_node(n);//初始化二级回收链表
	}
	return 1;static inline int alloc_kmem_cache_cpus(struct kmem_cache *s)
{
	...
	//为cpu_slab分配内存块空间
	s->cpu_slab = __alloc_percpu(sizeof(struct kmem_cache_cpu),
				     2 * sizeof(void *));

	if (!s->cpu_slab)
		return 0;
	//初始tid
	init_kmem_cache_cpus(s);

	return 1;
}

static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
	int node;
	//从memory0中分配struct kmem_cache实体(这主要指s0或s1)
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

	memcpy(s, static_cache, kmem_cache->object_size);
	...
	for_each_node_state(node, N_NORMAL_MEMORY) {
		struct kmem_cache_node *n = get_node(s, node);
		struct page *p;

		if (n) {
			list_for_each_entry(p, &n->partial, lru)
				p->slab_cache = s;//二级回收链表上小缓存池归属于那类大缓存池
		...
		}
	...
	}
	list_add(&s->list, &slab_caches);//加入同类缓存池链表
	return s;
}


2、slub分配和释放分析

首先看下同类缓存池,小缓存池,小内存块组成的结构,或者说模型,如下图:
在这里插入图片描述                  图二
代码是如何描述图中的结构,看下面慢慢分析:

每次分配内存时,都是通过申请size查找到对应的同类缓存池,再从同类缓存池中找到小缓存池,最后从小缓存池中找到空闲小内存块,并将小内存块起始地址返回给申请者.那么究竟是从s->cpu_slab->page, s->cpu_slab->partial, s->node[x]->partial三个中那个获取到小内存块,且看下面分析:

s->cpu_slab:
当前小内存块链头指针freelist
当前小缓存池指针page
一级回收链表partial

s->cpu_slab->freelist :
如图二,我们把freelist指向的,多个相互连接的小内存块,叫做小内存块链.
s->cpu_slab->freelist始终指向当前小缓存池里面小内存块链上第一个可用小内存块,即小缓存池首个小内存块.如图,小缓存池里面小内存块是按照"slub核心原理"描述方式串联在一起的,关于分配和释放,在"slub核心原理"节也有描述.

s->cpu_slab->page :
指向当前小缓存池首页.
在当前小缓存池上分配内存,主要函数slab_alloc_node(),定义在mm/slub.c中:
函数调用过程:
kmalloc() --> slab_alloc() --> slab_alloc_node()

static __always_inline void *slab_alloc_node(struct kmem_cache *s,
		gfp_t gfpflags, int node, unsigned long addr)
{
	void **object;
	struct kmem_cache_cpu *c;
	struct page *page;
	unsigned long tid;
	...	
	c = __this_cpu_ptr(s->cpu_slab);
	...
	object = c->freelist;//小缓存池上首个可用小内存块
	page = c->page;//当前小缓存池首页
	if (unlikely(!object || !node_match(page, node)))//object为NULL时表示当前小缓存池没有可用小内存块,需要从其他地方分配内存
		object = __slab_alloc(s, gfpflags, node, addr, c);

	else {//当前小缓存池有空闲小内存块,从当前小缓存池中分配获取小内存块
		void *next_object = get_freepointer_safe(s, object);//获取当前小缓存池上,下一个可用小内存块地址
		...
		/*
		this_cpu_cmpxchg_double()将做如下操作:
		s->cpu_slab->freelist = next_object;
		s->cpu_slab->tid = tid;
		即s->cpu_slab->freelist指向小缓存池中下一个可用小内存块		
		*/
		if (unlikely(!this_cpu_cmpxchg_double(
				s->cpu_slab->freelist, s->cpu_slab->tid,
				object, tid,
				next_object, next_tid(tid)))) {
				...
		}
		...
	}
	...	
	return object;//返回可用小内存块首地址
}

小内存块释放,发生在当前小缓存池上,主要函数slab_free(), 定义在mm/slub.c:
函数调用过程:
kfree() --> slab_free()

void kfree(const void *x)
{
	struct page *page;
	void *object = (void *)x;//释放内存地址	
	...
	page = virt_to_head_page(x);//通过释放内存虚拟地址,查找到小内存所在小缓存池,即小缓存池首页地址
	...
	slab_free(page->slab_cache, page, object, _RET_IP_);
}

static __always_inline void slab_free(struct kmem_cache *s,
			struct page *page, void *x, unsigned long addr)
{
	void **object = (void *)x;//释放内存地址首地址
	struct kmem_cache_cpu *c;
	unsigned long tid;
	...
	c = __this_cpu_ptr(s->cpu_slab);
	...
	if (likely(page == c->page)) {//释放内存,属于当前小缓存池
		set_freepointer(s, object, c->freelist);//c->freelist被写入到释放小内存块object空间头部,
		                        //这也是小内存块串联在一起的关键,就如"slub核心原理"描述的
		
		//this_cpu_cmpxchg_double执行主要操作:
		//s->cpu_slab->freelist = object;刚释放的小内存块,被放在小内存块链上第一个位置
		//s->cpu_slab->tid = next_tid(tid);		
		if (unlikely(!this_cpu_cmpxchg_double(
				s->cpu_slab->freelist, s->cpu_slab->tid,
				c->freelist, tid,
				object, next_tid(tid)))) {
				...			
		}
		...
	} else
		__slab_free(s, page, x, addr);
}

从以上可以看出:

if (likely(page == c->page)) {//释放内存,属于当前小缓存池

当释放的小内存块,属于当前缓存池c->page时,释放才会发生在当前小缓存池上,即把释放的小内存块,回收到当前缓存池.

内存分配和释放,发生在当前缓存池,速度是最快的.
下面,将会分析,不是发生在当前缓存池的情况.

s->cpu_slab->partial :
如图二,多个相互有连接的小缓存池,我们取名为小缓存池链
s->cpu_slab->partial始终指向小缓存池链头
当前缓存池s->cpu_slab->page指向的小缓存池里面小内存块被分配使用完时, 会从一级回收链表s->cpu_slab->partial上取小缓存池, 给到s->cpu_slab->page上,即切换到新的小缓存池,s->cpu_slab->freelist也会指向新小缓存池里面小内存块链上头个小内存块.

函数slab_alloc_node()在当前缓存池耗尽时,会调用__slab_alloc()进一步找寻空闲内存:

static __always_inline void *slab_alloc_node(struct kmem_cache *s,
		gfp_t gfpflags, int node, unsigned long addr)
{
	void **object;
	struct kmem_cache_cpu *c;
	struct page *page;
	unsigned long tid;
	...
	c = __this_cpu_ptr(s->cpu_slab);
	...
	object = c->freelist;
	page = c->page;
	...
	if (unlikely(!object || !node_match(page, node)))//object为NULL时,表示小缓存池里面小内存块耗尽
		object = __slab_alloc(s, gfpflags, node, addr, c);

	else {//从当前小缓存池分配内存
	...
	}
	...
}

__slab_alloc()首先从一级回收链表s->cpu_slab->partial上取小缓存池,并在小缓存池中找寻空闲小内存块:

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
			  unsigned long addr, struct kmem_cache_cpu *c)
{
	void *freelist;
	struct page *page;
	unsigned long flags;
	...
	c = this_cpu_ptr(s->cpu_slab);
	...
	page = c->page;//当前小缓存池
	if (!page)
		goto new_slab;
redo:
	...
	freelist = get_freelist(s, page);//第一次执行时,当前小缓存池已经没有了空闲小内存块,返回的freelist为NULL,
	                                 //只有通过下面goto redo跳转,再次执行时,
	                                 //page指向新的小缓存池,如存在空闲小内存块,返回的freelist才会是非NULL

	if (!freelist) {
		c->page = NULL;//清空指向当前小缓存池的指针
		stat(s, DEACTIVATE_BYPASS);
		goto new_slab;//跳转到切换当前小缓存池的地方
	}
	...
load_freelist://执行到这里,表示已经找到空闲小内存块
	...
	c->freelist = get_freepointer(s, freelist);//freelist是指向小内存块链上第一块,
	                          //get_freepointer返回后,c->freelist指向小内存块链上第二块
	                          //get_freepointer()函数,就是从小内存块链上取小内存块的过程,并返回下一块
	c->tid = next_tid(c->tid);
	...
	return freelist;//成功获取小内存块,返回
	
new_slab:

	if (c->partial) {//小缓存池链上,如有小缓存池
		page = c->page = c->partial;//从c->partial上取新的小缓存池,即切换当前小缓存池
		c->partial = page->next;//c->partial指向小缓存池链上,下一个小缓存池
		stat(s, CPU_PARTIAL_ALLOC);
		c->freelist = NULL;//清空当前小内存块链指针,为切换当前小内存块链做准备
		goto redo;//切换当前小缓存池后,跳转准备再次查找空闲小内存块.
	}
	...

函数get_freelist():

static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{
	struct page new;
	unsigned long counters;
	void *freelist;

	do {
		freelist = page->freelist;//从当前小缓存池上取小内存块链头
		                //page->freelist始终指向小内存块链头
		                          //小缓存池从伙伴系统分配来,切换为当前小缓存
		                          //池时,page->freelist会被置为NULL
		counters = page->counters;

		new.counters = counters;
		VM_BUG_ON(!new.frozen);

		new.inuse = page->objects;
		new.frozen = freelist != NULL;//page为当前小缓存池时,freelist为
		                  //NULL,那么new.frozen=0,
		                  //frozen为0表示解冻,可被释放,为1时
		                  //表示冻住,小缓存池不可被释放,
		                  //这里的释放,是指释放回伙伴系统
		                  //page不为当前小缓存池时,如果有空闲
		                  //小内存块,则new.fozen=1,没有空闲
		                  //小内存块时,new.fozen=0

	//__cmpxchg_double_slab函数主要作用:	
	//page->freelist=NULL
	//page->counters=new.counters,即修改page->counter值
	} while (!__cmpxchg_double_slab(s, page,
		freelist, counters,
		NULL, new.counters,
		"get_freelist"));

	return freelist;//返回小内存块链头地址
}

在上面分配过程中,s->cpu_slab->page指向的当前小缓存池,在切换到新小缓存池时,旧的小缓存池,似乎直接被抛弃,那么,slub后面是怎么知道旧小缓存池的存在?请看小内存块释放,是怎么发生在一级回收链表s->cpu_slab->partial上:
函数调用过程:
kfree() --> slab_free() --> __slab_free() --> put_cpu_partial()

void kfree(const void *x)
{
	struct page *page;
	void *object = (void *)x;//释放小内存块的地址
	...
	page = virt_to_head_page(x);//通过内存地址,查找到内存所在页的首页地址,
	                 //即,被释放小内存块,所在小缓存池首页地址
	...
	//同类缓存池page->slab_cache
	//被释放内存所在小缓存池首页page
	//被释放object小内存块起始地址
	slab_free(page->slab_cache, page, object, _RET_IP_);
}

static __always_inline void slab_free(struct kmem_cache *s,
			struct page *page, void *x, unsigned long addr)
{
	void **object = (void *)x;
	struct kmem_cache_cpu *c;
	unsigned long tid;
	...
	c = __this_cpu_ptr(s->cpu_slab)
	...
	if (likely(page == c->page)) {//当前缓存池
	...
	}else
		__slab_free(s, page, x, addr);//释放内存,不在当前小缓存池中
}

//__slab_free需要关注:
//同一个page小缓存池里面小内存块分配完后,
//是否为首次发生小内存块释放
static void __slab_free(struct kmem_cache *s, struct page *page,
			void *x, unsigned long addr)
{
	void *prior;
	void **object = (void *)x;
	int was_frozen;
	struct page new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long uninitialized_var(flags);
	...
	do {
		...
		prior = page->freelist;//page小缓存池,里面小内存块分配完后,
		                       //首次发生小内存块释放时,page->freelist为NULL
		                       //不是首次释放时,表示小缓存池,前面发生过小内存块释放,page->freelist不为NULL
		counters = page->counters;
		set_freepointer(s, object, prior);//将prior写入到object空间开头地方
		new.counters = counters;
		was_frozen = new.frozen;//page小缓存池,里面小内存块分配完后,
		               //首次发生小内存块释放时,new.frozen=0,可看切换当前
		               //小缓存池时get_freelist()函数的调用
		               //不是首次释放时,new.frozen=1
		new.inuse--;//inuse表示小缓存池里面有多少个小内存块被分配使用中,
		       //释放时,自然就需要减去1
		if ((!new.inuse || !prior) && !was_frozen) {//此条件,在page小缓存池,里面小内存块分配完,
		                           //首次发生小内存块释放时,将会为真
		                           //不是首次释放小内存块时,将为假

			//kmem_cache_debug(s)主要用于slub调试时打开CONFIG_SLUB_DEBUG,
			//正常情况下CONFIG_SLUB_DEBUG是不被打开的			
			if (!kmem_cache_debug(s) && !prior)//page上首次发生小内存块释放时,此条件为真,
			                     //不是首次发生小内存块释放,则为假

				/*
				 * Slab was on no list before and will be partially empty
				 * We can defer the list move and instead freeze it.
				 */
				new.frozen = 1;

			else { /* Needs to be taken off a list */

	                        n = get_node(s, page_to_nid(page));
				/*
				 * Speculatively acquire the list_lock.
				 * If the cmpxchg does not succeed then we may
				 * drop the list_lock without any processing.
				 *
				 * Otherwise the list_lock will synchronize with
				 * other processors updating the list of slabs.
				 */
				spin_lock_irqsave(&n->list_lock, flags);

			}
		}

	} while (!cmpxchg_double_slab(s, page,//cmpxchg_double_slab主要完成:
	                       //page->freelist=object
	                       //page->counters=new.counters
		prior, counters,
		object, new.counters,
		"__slab_free"));

	if (likely(!n)) {//此条件,基本为真

		/*
		 * If we just froze the page then put it onto the
		 * per cpu partial list.
		 */
		if (new.frozen && !was_frozen) {//小缓存池page在一级回收链表上,
		                   //首次发生小内存块释放时,
		                   //此条件为真,不是首次释放时,为假
			put_cpu_partial(s, page, 1);//将小缓存池page放到s->cpu_slab->partial链表上			
			stat(s, CPU_PARTIAL_FREE);
		}
		/*
		 * The list lock was not taken therefore no list
		 * activity can be necessary.
		 */
        if (was_frozen)
            stat(s, FREE_FROZEN);
        return;
	}
	...//put_cpu_partial将把小缓存池,放到s->cpu_slab->partial
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
	struct page *oldpage;
	int pages;
	int pobjects;

	do {
		pages = 0;
		pobjects = 0;
		oldpage = this_cpu_read(s->cpu_slab->partial);//一级回收链表

		if (oldpage) {//同类缓存池,首次发生小内存块释放,oldpage为NULL
		              //之后,发生的小内存块释放,oldpage将为非NULL
			pobjects = oldpage->pobjects;//一级回收链表上,小内存块总数
			pages = oldpage->pages;//一级回收链表上,小缓存池总数
			//一级回收链表上,小内存块总数超过cpu_partial时,
			//将会把一级回收链表上的小缓存池,取出放入到二级回收链表上
			//注意:__slab_free()调用本函数,drain是为1
			if (drain && pobjects > s->cpu_partial) {
				unsigned long flags;
				/*
				 * partial array is full. Move the existing
				 * set to the per node partial list.
				 */
				local_irq_save(flags);
				//将一级回收链表上小缓存池,取出放入到二级
				unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
				local_irq_restore(flags);
				oldpage = NULL;
				pobjects = 0;
				pages = 0;
				stat(s, CPU_PARTIAL_DRAIN);
			}
		}

		pages++;//统计一级回收链表上,小缓存池数
		pobjects += page->objects - page->inuse;//统计一级回收链表上,空闲小内存块数
												//page->objects小缓存池上小内存块总数,从伙伴系统分配时确定,之后一直不变
												//page->inuse小缓存池已分配使用的小内存块数
		page->pages = pages;//将统计的小缓存池数信息,存放到一级回收链表头中
		            //由此可看出,一级回收链表上小缓存池总数信息,始终存放在链表头中
		page->pobjects = pobjects;//将统计的小内存块数,存放到一级回收链表头中
		                //由此可看出,一级回收链表上小内存块总数信息,始终存放在链表头中
		page->next = oldpage;//新加入的小缓存池,放入一级回收链表头

	//this_cpu_cmpxchg主要操作:
	//s->cpu_slab->partial=page
	} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page) != oldpage);
}

s->node[x]->partial :
二级回收链表
在什么情况下,才会从二级回收链表分配获取小缓存池呢?
在什么情况下,才会把小缓存池放入到二级回收链表呢?

从上面分析中,可以看到分配调用过程,有个函数__slab_alloc(),在这个函数里面,根据一定条件满足,将从二级回收链表分配内存:

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
			  unsigned long addr, struct kmem_cache_cpu *c)
{
	void *freelist;
	struct page *page;
	unsigned long flags;
	...
	c = this_cpu_ptr(s->cpu_slab);
	...
load_freelist://执行到这里,表示已经成功切换到有空闲小内存块的小缓存池
	/*
	 * freelist is pointing to the list of objects to be used.
	 * page is pointing to the page from which the objects are obtained.
	 * That page must be frozen for per cpu allocations to work.
	 */
	VM_BUG_ON(!c->page->frozen);
	c->freelist = get_freepointer(s, freelist);
	c->tid = next_tid(c->tid);
	local_irq_restore(flags);
	return freelist;
	
new_slab:

	if (c->partial) {//c->partial为NULL时,表示一级级回收链表无小缓存池,
	          //将进入下面的code,从二级回收链表上分配获取小缓存池
		...
	}
	//将有可能从二级回收链表上获取小缓存池
	freelist = new_slab_objects(s, gfpflags, node, &c);
	...
	page = c->page;//此时当前小缓存池已经切换到新小缓存池
	if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))
		goto load_freelist;//跳转
	...
}

static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,
			int node, struct kmem_cache_cpu **pc)
{
	void *freelist;
	struct kmem_cache_cpu *c = *pc;
	struct page *page;

	freelist = get_partial(s, flags, node, c);//从二级回收链表上分配获取内存

	if (freelist)
		return freelist;

	page = new_slab(s, flags, node);//当前小缓存池,一级,二级回收链表上都没
	                   //有空闲小内存块,将会从伙伴系统分配
	                   //获取新小缓存池
	if (page) {//成功从伙伴系统中分配获取到小缓存池
		c = __this_cpu_ptr(s->cpu_slab);
		if (c->page)
			flush_slab(s, c);//清空当前小缓存池和小内存块链
			          //并且根据条件,是否将小缓存池释放会伙伴系统

		/*
		 * No other reference to the page yet so we can
		 * muck around with it freely without cmpxchg
		 */
		freelist = page->freelist;//新小缓存池中小内存块链
		page->freelist = NULL;//情况当前小缓存池中小内存块链,表示小内存块都被用
		             //page->freelist始终指向未被用小内存块链

		stat(s, ALLOC_SLAB);
		c->page = page;//新小缓存池,作为当前小缓存池
		*pc = c;
	} else
		freelist = NULL;

	return freelist;
}

static void *get_partial(struct kmem_cache *s, gfp_t flags, int node,
		struct kmem_cache_cpu *c)
{
	void *object;
	int searchnode = (node == NUMA_NO_NODE) ? numa_node_id() : node;

	//从二级回收链表上分配获取小缓存池
	object = get_partial_node(s, get_node(s, searchnode), c, flags);
	if (object || node != NUMA_NO_NODE)
		return object;

	...
}

static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
				struct kmem_cache_cpu *c, gfp_t flags)
{
	struct page *page, *page2;
	void *object = NULL;
	int available = 0;
	int objects;

	/*
	 * Racy check. If we mistakenly see no partial slabs then we
	 * just allocate an empty slab. If we mistakenly try to get a
	 * partial slab and there is none available then get_partials()
	 * will return NULL.
	 */
	if (!n || !n->nr_partial)//二级回收链表是否为空
	                    //n指向二级回收链表
	                    //n->nr_partial二级回收链表上小缓存池数量
		return NULL;

	spin_lock(&n->list_lock);
	//page指向前一个小缓存池,page2指向下一个小缓存池
	//n->partial二级回收链表
	//这个循环将把二级回收链表上,第一个小缓存池作为当前小缓存池,其他的放到二级回收链表
	list_for_each_entry_safe(page, page2, &n->partial, lru) {
		void *t;

		if (!pfmemalloc_match(page, flags))
			continue;
		
		//从二级回收链表上获取空闲小内存块链t
		t = acquire_slab(s, n, page, object == NULL, &objects);
		if (!t)
			break;

		available += objects;//统计小内存块数量
		if (!object) {
			c->page = page;//第一个,从二级回收链表获取的小缓存池作为当前小缓存池
			stat(s, ALLOC_FROM_PARTIAL);
			object = t;
		} else {
			put_cpu_partial(s, page, 0);//将后续从二级回收链表上获取的小缓存
			                        //池全部取出来放入到一级回收链表上
			stat(s, CPU_PARTIAL_NODE);
		}
		if (kmem_cache_debug(s) || available > s->cpu_partial / 2)
			break;

	}
	spin_unlock(&n->list_lock);
	return object;
}

static inline void *acquire_slab(struct kmem_cache *s,
		struct kmem_cache_node *n, struct page *page,
		int mode, int *objects)
{
	void *freelist;
	unsigned long counters;
	struct page new;

	/*
	 * Zap the freelist and set the frozen bit.
	 * The old freelist is the list of objects for the
	 * per cpu allocation list.
	 */
	freelist = page->freelist;
	counters = page->counters;
	new.counters = counters;
	*objects = new.objects - new.inuse;//计算空闲小内存块数量
	if (mode) {//mode非0,表示二级回收链表上第一个小缓存池
		new.inuse = page->objects;
		new.freelist = NULL;
	} else {//二级回收链表上,其他小缓存池
		new.freelist = freelist;
	}

	VM_BUG_ON(new.frozen);
	new.frozen = 1;//表示小缓存池被冻结,不能释放回伙伴系统

	//page->freelist = new.freelist
	//page->counters=new.counters
	if (!__cmpxchg_double_slab(s, page,
			freelist, counters,
			new.freelist, new.counters,
			"acquire_slab"))
		return NULL;

	remove_partial(n, page);//从二级回收链表上移除小缓存池
	WARN_ON(!freelist);
	return freelist;
}

slub释放内存时,如何将内存释放到二级回收链表上?看如下:
函数调用过程:
kfree() --> slab_free() --> __slab_free() --> put_cpu_partial()

static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
	struct page *oldpage;
	int pages;
	int pobjects;
	...
	//pobjeccts表示一级回收链表上空闲小内存块数量
	//s->cpu_partial表示一级回收链表上空闲小内存块数量上限,大于上限将会移动到二级回收链表
	if (drain && pobjects > s->cpu_partial) {
				unsigned long flags;
		...
		//将一级回收链表上小缓存池,移动到二级回收链表
		unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
		...
	}
	...
}

static void unfreeze_partials(struct kmem_cache *s,
		struct kmem_cache_cpu *c)
{
	struct kmem_cache_node *n = NULL, *n2 = NULL;
	struct page *page, *discard_page = NULL;

	while ((page = c->partial)) {//循环从一级回收链表上获取小缓存池
		struct page new;
		struct page old;

		c->partial = page->next;//修改一级回收链表头指针

		n2 = get_node(s, page_to_nid(page));//n2指向二级回收链表
		if (n != n2) {
			if (n)
				spin_unlock(&n->list_lock);

			n = n2;
			spin_lock(&n->list_lock);
		}

		do {

			old.freelist = page->freelist;
			old.counters = page->counters;
			VM_BUG_ON(!old.frozen);

			new.counters = old.counters;
			new.freelist = old.freelist;

			new.frozen = 0;//清楚冻结标志

		//page->freelist=new.freelist
		//page->counters=new.counters
		} while (!__cmpxchg_double_slab(s, page,
				old.freelist, old.counters,
				new.freelist, new.counters,
				"unfreezing slab"));

		//n->nr_partial二级回收链表上,小缓存池数量
		// s->min_partial二级回收链表上,小缓存池数量上限,超过将被释放回到伙伴系统
		//new.inuse为0时,表示小缓存池上,没有小内存块被分配使用
		if (unlikely(!new.inuse && n->nr_partial > s->min_partial)) {
			page->next = discard_page;
			discard_page = page;
		} else {
			add_partial(n, page, DEACTIVATE_TO_TAIL);//将小缓存池page加入到二级回收链表n上
			stat(s, FREE_ADD_PARTIAL);
		}
	}

	if (n)
		spin_unlock(&n->list_lock);

	while (discard_page) {//将到达n->nr_partial > s->min_partial条件的
	             //二级缓存回收链表上小缓存池将被释放回伙伴系统
		page = discard_page;
		discard_page = discard_page->next;

		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, page);//释放回伙伴系统
		stat(s, FREE_SLAB);
	}
}

从当前小缓存池,一级,二级回收链表上,都无法寻找到具有空闲小内存块的缓存池时,将会从伙伴系统分配获取小缓存池:
函数调用:
kmalloc() --> slab_alloc() --> slab_alloc_node() --> __slab_alloc() --> new_slab_objects() --> new_slab()

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	struct page *page;
	void *start;
	void *last;
	void *p;
	int order;
	...
	//从伙伴系统中分配内存页,作为小缓存池
	page = allocate_slab(s,
		flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
	if (!page)
		goto out
	...
	page->slab_cache = s;//小缓存池page,属于同类缓存池s
	__SetPageSlab(page);
	if (page->pfmemalloc)
		SetPageSlabPfmemalloc(page);

	start = page_address(page);//通过内存页计算内存页首地址
	...
	last = start;
	//将分配获取到的小缓存池,按照"slub核心原理"方式初始化,也可以视为格式化小缓存池
	for_each_object(p, s, start, page->objects) {
		setup_object(s, page, last);
		set_freepointer(s, last, p);
		last = p;
	}
	setup_object(s, page, last);
	set_freepointer(s, last, NULL);//最后一个小内存块,头部空间写入NULL

	page->freelist = start;//小缓存池中,第一个小内存块起始地址
	page->inuse = page->objects;//小缓存池中小内存块总数量objects
	page->frozen = 1;//小缓存池被冻结,不能释放回伙伴系统
out:
	return page;static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	struct page *page;
	struct kmem_cache_order_objects oo = s->oo;
	gfp_t alloc_gf
	...
	flags |= s->allocflags;

	/*
	 * Let the initial higher-order allocation fail under memory pressure
	 * so we fall-back to the minimum order allocation.
	 */
	alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;

	page = alloc_slab_page(alloc_gfp, node, oo);//小缓存池空间大小,oo是2的幂指数
	if (unlikely(!page)) {//按照oo大小分配失败,将降低为s->min继续分配
		oo = s->min;
		/*
		 * Allocation may have failed due to fragmentation.
		 * Try a lower order alloc if possible
		 */
		page = alloc_slab_page(flags, node, oo);

		if (page)
			stat(s, ORDER_FALLBACK);
	}
	...
	page->objects = oo_objects(oo);//计算小缓存池里面小内存块总数
	...
	return page;
}

简单总结:

slub分配时小内存块计算:
也可以说,申请size小内存块,查找属于那类大缓存池(同类缓存池).
第一种办法:
(1)kmalloc_index()得到kmalloc_caches[]角标index.
(2)然后kmalloc_caches[index]就是同类缓存池,之后见上面"slub分配释放"分析.
第二种办法:
通过调用kmalloc_slab()直接获取,同样,之后见上面"slub分配释放"分析.

slub分配内存时:
首先在当前小缓存池上检查有没有空闲小内存块,有则直接分配,如没有,检查一级回收链表上有没有小缓存池,有,则把该小缓存池切换为当前小缓存池,然后分配,如果一级回收链表还是没有,继续检查二级回收链表上小缓存池,同样,有,则切换为当前小缓存池,然后分配,要是二级回收链表上都还是没有,最后,向伙伴系统申请分配新的小缓存池,并将新分配的小缓存池切换为当前小缓存池,再分配.

slub释放内存:
释放小内存块,属于当前小缓存池时,直接释放回当前小缓存池.
释放小内存块,不属于当前小缓存池时,会被释放回所所属小缓存池,如果该小缓存池是分配使用完后,首次发生小内存块释放,那么将会把小缓存池挂接到一级回收链表上.每当有其他小缓存池发生挂接到一级回收链表时,会借此时机,先检查一级回收链表上小内存块总数是否大于s->cpu_partial,如果大于,将会把一级回收链表上所有小缓存池移动到二级回收链表上,移动过程中,还会检查二级回收链表上小缓存池数,如果超过s->min_partial,将会把超过的小缓存池(里面小内存块都未被分配使用情况)释放回伙伴系统,然后再将该小缓存池放入到一级回收链表.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值