欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

内存管理---Slab原理分析

程序员文章站 2022-03-07 20:01:25
...

概念介绍

什么是slab?

slab是slab内存分配器从buddy system申请页面的基本单位。然而slab的大小不是固定的,slab从属于某个kmem cache实例,不同的kmem cache实例,其slab的大小是不同的。slab的大小必须是2^order个pages,order不能超过buddy system所支持的最大的order。

slab内存分配器从buddy system分配了slab之后,会将其挂在对应的kmem cache实例的node节点。

什么是object?

object是slab内存分配器对外提供的申请内存的基本单位。slab内存分配器从buddy system申请了buddy之后,会将其拆分成一个个object,并缓存在kmem cache实例的cpu_cache中,用户申请内存时,其实获取的就是一个个object。

一旦object缓存耗尽,就会重新从buddy system申请slab,并将其拆分成object,放入内存池。

什么是cache?

slab内存分配器中的cache跟硬件cache无关,是一个纯软件的概念。slab内存分配器有两种cache,一个是slab的cache,一个是object的cache。slab内存分配器从buddy system获取页面后,会将其加入kmem cache的node节点,这个就是slab的cache;将slab拆分成多个object,并将object加入kmem cache的cpu_cache内存池,这个就是object的cache;可以看到这两种cache实际是对共同的物理页面的两种缓存形式。

原理分析

作用

slab是为了进行更细粒度的内存分配,满足小于一页的内存分配需求,将页拆分为更小的单位。
Linux中支持slab、slub、slob。 一般在使用kmalloc进行内存分配时就使用到了slab分配器。
Slab分配器和伙伴系统以及内核调用接口之间的关联如下:

内存管理---Slab原理分析

Slab组成:

Slab分为管理数据结构kmem_cache和保存各个object的各个slab,如下图所示:
内存管理---Slab原理分析内核中所有的kmem_cache通过cache_chain连接起来。
内核初始化的时候首先创建了一个cache_cache的静态kmem_cache结构,内核后续创建新的kmem时候直接从这个cache_cache管理slab的obj中取出。

Struct kmem_cache:(以4.4.6内核为例)

struct kmem_cache {
	unsigned int object_size;/* The original size of the object */
	unsigned int size;	/* The aligned/padded/added on size  */
	unsigned int align;	/* Alignment as calculated */
	unsigned long flags;	/* Active flags on the slab */
	const char *name;	/* Slab name for sysfs */
	int refcount;		/* Use counter */
	void (*ctor)(void *);	/* Called on object slot creation */
	struct list_head list;	/* List of all slab caches on the system */
};

struct kmem_list3 在4.x的内核中为kmem_cache_node:

struct kmem_cache_node {
	spinlock_t list_lock;

#ifdef CONFIG_SLAB
	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 */
	struct array_cache *shared;	/* shared per node */
	struct alien_cache **alien;	/* on other nodes */
	unsigned long next_reap;	/* updated without locking */
	int free_touched;		/* updated without locking */
#endif

#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

};

Struct array_cache:(内核为每个CPU都提供一个array_cache实例)

struct array_cache {
	unsigned int avail;
	unsigned int limit;
	unsigned int batchcount;
	unsigned int touched;
	void *entry[];	/*
			 * Must have this definition in here for the proper
			 * alignment of array_cache. Also simplifies accessing
			 * the entries.
			 *
			 * Entries should not be directly dereferenced as
			 * entries belonging to slabs marked pfmemalloc will
			 * have the lower bits set SLAB_OBJ_PFMEMALLOC
			 */
};

这些数据结构间的关系:
内存管理---Slab原理分析我们主要关心数据结构之间的连接关系,所以先看kmem_cache中的kmem_list3和array_cache这两个成员。
struct kmem_list3
kmem_list3是per node类型数据,一个node对应一个kmem_list3结构,我们的系统是UMA,所以只有一个kmem_list3结构。
kmem_list3中有三个链表slabs_partial、slabs_full、slabs_free,每个链表中挂着都是slab结构。其中,已经有部分obj被分配出去的slab均挂在slabs_partial下;全部obj都被分配出去的slab挂在slabs_full下;全部obj都未分配出去的slab挂在slabs_free下。
struct array_cache
array_cache是per cpu类型数据,每个core对应一个array_cache结构。
当array_cache中的obj为空时,系统会以batchcount值为准,一次性从kmem_list3中搬运batchcount个obj到arry_cache中,存放在entry里。
slab
slab主要包含两大部分,管理性数据和obj对象,其中管理性数据包括struct slab和kmem_bufctl_t。
slab有两种形式的结构,管理数据外挂式或内嵌式。如果obj比较小,那么struct slab和kmem_bufctl_t可以和obj分配在同一个物理page中,可称为内嵌式;如果obj比较大,那么管理性数据需要单独分配一块内存来存放,称之为外挂式。我们在上图中所画的slab结构为内嵌式。

Kmem_bufctl_t

管理性数据kmem_bufctl_t的类型是unsigned int,本质上是一个空闲obj链表,用于描述下一个可用obj序号。初始化时,当前slab中的obj均可用,所以图中kmem_bufctl_t中的值依次就是下一个可用的obj序。
kmem_bufctl_t中使用方法可参考下图场景。
内存管理---Slab原理分析系统初始化时slab->free执行0#slab,某时刻系统已将0#—3# obj分配出去,此时slab->free指向4# slab,而4#slab对应的kmem_bufctl_t中存放的值是5。表明slab中current可用obj是4#,next可用obj是5#。
系统运行一段时间后,1# obj需回收。此时,1#obj对应的kmem_bufctl_t中填入slab->free值4,并将slab->free修正为1。这表明slab中current可用obj是1#,next可用的obj为4#。

SLAB分配流程以及管理逻辑

内存管理---Slab原理分析obj分配优先考虑从array_cache的entry中取,取的规则遵循LIFO原则,先从enry的末尾取出obj,因为这个obj很有可能还在硬件cache中,是热的。如果entry为空,则说明是第一次从array_cache中分配obj或者是array_cache中的所有obj都已分配,所以需要先从kmem_cache的kmem_list3中取出batchcount个obj,把这些obj全部填充到enty中,然后再分配。
obj释放也是优先考虑释放到array_cache中,而不是直接释放到kmem_list3中。只有array_cache中的obj超过了limit上限,系统才会将enty中的头batchcount个obj搬到kmem_cache所在的kmem_list3中,然后将entry中剩余obj向前移动,然后再将准备释放的obj放到entry的末尾。

相关标签: 内存管理