slab分配器学习系列之linux2.6

本文详细介绍了Linux 2.6内核中的slab分配器,包括slab的主要数据结构,如kmalloc、kmem_cache_init和cache_grow等。文章通过与内存桶的对比,阐述了slab分配器如何通过kmem_cache管理和slab链表进行内存管理,以及slab分配的过程。slab分配器采用局部缓存array_cache,减少了多核竞争带来的开销,并通过slab_map_pages和cache_init_objs等函数进行初始化和对象管理。
摘要由CSDN通过智能技术生成

前言

前文通过存储桶了解了 slab 分配器最简单的实现方式,在后续的 linux 版本中,slab 分配器被正式提出,并用于解决内核中小样本分配问题。笔者打算先从 2.6.18 入手进行 slab 分配器的学习

主要参考资料为 《深入Linux内核架构》

slab 分配器

物理页并不是内存管理的最小单位,为了减少内部碎片,提出了 slab 分配机制。该机制其实是内存桶的复杂版,主要入口函数为 kmalloc 。其核心思想与内存桶类似,即在物理页上进一步划分连续的相同大小对象,并对此进行管理。在此使用 《简说Linux之slab机制》 做的图,能够很好地说明slab层的设计思想

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-33A3KIuD-1652862126784)(linux内存管理源码深入探究.assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARGFIdWFuZ1hpYW8=,size_20,color_FFFFFF,t_70,g_se,x_16.png)]

即,cache_chain 关联了多个不同大小的 kmem_cache,每个 kmem_cache 关联一个 slabs ,slabs 由多个 slab 组成,每个 slab 又对应多个 page,每个 page 按照 kmem_cache 对应大小平分了多个 object 空间。实际上,slab 的实现与 linux 0.12 版本中 内存桶的思想较为类似。 《深入Linux内核架构》中也有相关图示,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMvgQfLz-1652862126788)(linux内存管理源码深入探究.assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARGFIdWFuZ1hpYW8=,size_19,color_FFFFFF,t_70,g_se,x_16.png)]

  • kmem_cache :管理一整个结构。不同类型对象的 kmem_cache 不同,不同 size 也对应不同的 kmem_cache
  • array_cache: 用于进行 slab 缓存。当需要分配对象时,先从缓存中找,找不到再去 slab 空闲链表中获取。不同 cpu 对应不同 array_cache。这么做属于本地缓存,避免了多核竞争需要上锁带来的消耗。
  • slab链表:可以看到 kmem_cache 中涉及空闲、部分空闲、用尽三种类型的 slab 链表,其串联了相应的 slab 。要注意的是 slab 头部结构与 Obj 内容在同一页帧中

而每个 kmem_cache 又通过双向链表组织
在这里插入图片描述

主要数据结构

struct cache_sizes malloc_sizes[];
struct cache_sizes {
   
	size_t		 cs_size;
	kmem_cache_t	*cs_cachep;
	kmem_cache_t	*cs_dmacachep;
};
struct kmem_cache {
   
/* 1) per-cpu data, touched during every alloc/free */
	struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
	unsigned int batchcount;
	unsigned int limit;
	unsigned int shared;
	unsigned int buffer_size;
	struct kmem_list3 *nodelists[MAX_NUMNODES];
	gfp_t gfpflags;
	size_t colour;			/* cache colouring range */
	unsigned int colour_off;	/* colour offset */
	struct kmem_cache *slabp_cache;
	unsigned int slab_size;
	unsigned int dflags;		/* dynamic flags */
	const char *name;
	struct list_head next;
	// 省略部分属性
};
struct kmem_list3 {
   
	struct list_head slabs_partial;	/* partial list first, better asm code */
	struct list_head slabs_full;
	struct list_head slabs_free;
	unsigned long free_objects;
	unsigned int free_limit;
	unsigned int colour_next;	/* Per-node cache coloring */
	spinlock_t list_lock;
	struct array_cache *shared;	/* shared per node */
	struct array_cache **alien;	/* on other nodes */
	unsigned long next_reap;	/* updated without locking */
	int free_touched;		/* updated without locking */
};
struct array_cache {
   
	unsigned int avail;
	unsigned int limit;
	unsigned int batchcount;
	unsigned int touched;
	spinlock_t lock;
	void *entry[0];	/*  柔性数组
};
struct slab {
	struct list_head list;
	unsigned long colouroff;
	void *s_mem;		/* including colour offset */
	unsigned int inuse;	/* num of objs active in slab */
	kmem_bufctl_t free;
	unsigned short nodeid;
};

对于上述各结构之间的关系,笔者绘制如下关系图进行描述
在这里插入图片描述
简单地,slab 分配过程为:

  1. 遍历 malloc_size 找到合适大小的 kmem_cache
  2. 先从 kmem_cache 的 array 判断 entry 中是否有空闲的 obj ,若有则直接返回地址
  3. 若无则获取 nodelists 中的 slabs_partial ,遍历 slab 看是否有可用的 obj,找到后将 obj 放入 entry 中,并返回地址

接下来会根据源码对上述过程进行细化

kmalloc (2.6.18)

slab分配器的分配入口为 kmalloc,其调用链如下

kmalloc
    --- __kmalloc ==> __do_kmalloc
    	--- __find_general_cachep : 根据 size 和 flags, 从 malloc_sizes 中获取对应的 kmem_cache
    	--- __cache_alloc ==> ____cache_alloc : 根据 kmem_cache 进行分配
    

__find_general_cachep 很简单,通过 size 遍历 malloc_sizes 找到合适的 kmem_cache 即可

static inline struct kmem_cache *__find_general_cachep(size_t size,
							gfp_t gfpflags)
{
   
	struct cache_sizes *csizep = malloc_sizes;
	while (size > csizep->cs_size)
		csizep++;
	if (unlikely(gfpflags & GFP_DMA))
		return csizep->cs_dmacachep;
	return csizep->cs_cachep;
}

____cache_alloc 函数中,首先通过 cpu_cache_get 获取到对应 kmem_cache 在当前 CPU 下的 array_cache。并判断是否为空,不为空直接获取 objp。否则要执行 cache_alloc_refill 从具体 slab 结构中获取。

具体地,array_cache 中的 entry 为指针数组,存放缓存 obj 的指针。跟踪 array_cache 的定义会发现,entry 被定义为 void *entry[0]。即这是一个长度为0的数组?实际上这是一个小知识点,该类型数组在 c语言中称为柔性数组,参考 《C语言柔性数组》,其只会出现在结构体最后一个属性,可以后续动态的为其申请不定长度的内存,进行使用。

static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
   
	void *objp;
	struct array_cache *ac;

	check_irq_off();
	
	ac = cpu_cache_get(cachep);
	if (likely(ac->avail)) {
   
		STATS_INC_ALLOCHIT(cachep);
		ac->touched = 1;
		objp = ac->entry[--ac->avail];
	} else {
   
		STATS_INC_ALLOCMISS(cachep);
		objp = cache_alloc_refill(cachep, flags);
		/*
		 * the 'ac' may be update
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值