Linux内存管理—Slab分配器

Slab分配器是Linux内核中为解决小块内存管理问题而设计的,它建立在伙伴系统之上,减少了内存碎片。Slab由slab、object和cache组成,其中slab是基本单位,object是分配给用户的内存单元。高速缓存分为普通和专用,通过kmem_cache_init()和kmem_cache_create()创建。Slab通过cache_grow()函数动态增长,使用kmem_cache_alloc()和kmem_cache_free()进行对象的申请和释放。Slab着色提高CPU缓存效率,内存回收通过kmem_cache_destroy()实现。
摘要由CSDN通过智能技术生成

Slab分配器的引入

内部碎片

内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间

内部碎片是处于区域内部或页面内部的存储块。占有这些区域或页面的进程并不使用这个存储块。而在进程占有这块存储块时,系统无法利用它。直到进程释放它,或进程结束时,系统才有可能利用这个存储块。

举例:某进程向系统申请了3K内存空间,系统通过伙伴系统算法可能分配给进程4K(一个标准页面)内存空间,导致剩余1K内存空间无法被系统利用,造成了浪费。这是由于进程请求内存大小与系统分配给它的内存大小不匹配造成的。

如下图所示:

由于伙伴算法采用页框(Page Frame)作为基本内存区,适合于大块内存请求。
但是在很多情况下,进程或者系统往往会以字节为单位请求内存,而非以页为单位请求内存,依然采用伙伴算法必然会造成系统内存的极大浪费。为满足进程或者系统对小片内存的请求(比如调用kmalloc()),对内存管理粒度更小的Slab分配器就产生了。

Slab分配器基础概念

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,一个是slab的cache,一个是object的cache。

  • slab内存分配器从buddy system获取页面后,会将其加入kmem cache的node节点,这个就是slab的cache
  • 将slab拆分成多个object,并将object加入kmem cache的cpu_cache内存池,这个就是object的cache
    这两种cache实际是对共同的物理页面的两种缓存形式。

注意:下文说明的缓存指的并不是真正的缓存,真正的缓存指的是硬件缓存,也就是我们通常所说的L1 cache、L2 cache、L3 cache,硬件缓存是为了解决快速的CPU和速度较慢的内存之间速度不匹配的问题,CPU访问cache的速度要快于内存,如果将常用的数据放到硬件缓存中,使用时CPU直接访问cache而不用再访问内存,从而提升系统速度。
下文中的缓存实际上是用软件在内存中预先开辟一块空间,使用时直接从这一块空间中去取,是Slab分配器为了便于对小块内存的管理而建立的。

Slab配器实际上是建立在伙伴系统算法之上的,Slab分配器使用的内存空间是通过伙伴算法进行分配的,只不过Slab对这些内存空间实现了自己的算法进而对小块内存进行管理。

它们之间的关系如下图所示

我们考虑下面场景:如果一个应用程序经常使用某一种类型的对象,或者说频繁的创建、回收某一种类型的对象,那我们是不是可以尝试将这类对象单独存放起来,当进程不再使用时,我们暂时先不回收,等应用程序再使用时,我们把之前应该回收的对象再拿出来,只不过重新构造一下对象,这样我们就减少了一次释放、申请内存的操作了。

Slab分配器的基本原理

slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。

  • 它的基本思想是将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。
  • 根据请求的频率不同,可以创建一组具有适当大小的专用对象来高效处理频繁请求的内存区,例如进程描述符,打开的文件对象等;
  • 对于很少遇到的内存区大小,可以基于一系列几何分布大小(如2的幂次方)的对象的分配模式来处理。
  • slab分配器把对象分组放进高速缓存,每个高速缓存都是同种类型对象的一种“储备”。
    例如,当一个文件被打开时,存放相应“打开文件”对象所需的内存区是从一个叫做filp的slab分配器的高速缓存中得到的。

每个高速缓存所占的内存区又被划分多个slab,每个 slab是由一个或多个连续的页框组成。每个页框中包含若干个对象,既有已经分配的对象,也包含空闲的对象。

它们的关系如下图所示:

在这里插入图片描述
如上图,各个高速缓存是通过双向链表连接,高速缓存中的每种slab也是通过双向链表连接,每个高速缓存都有一个空闲slab对象的本地高速缓存,请求内存区时就是通过这个本地高速缓存来分配对象,本地高速缓存从slab中的对象获取,一旦分配给本地高速缓存,对于slab来说,这个对象就是非空闲的(有可能还未被使用)

slab分配器的数据结构

高速缓存描述符kmem_cache_s

每个高速缓存通过kmem_cache_s结构来描述,这个结构中包含了对当前高速缓存各种属性信息的描述。所有的高速缓存通过双向链表组织在一起,形成高速缓存链表cache_chain。

struct kmem_cache_s {
   
/* 1) per-cpu data, touched during every alloc/free */
	/**
	 * 每CPU指针数组,指向包含空闲对象的本地高速缓存。
	 */
	struct array_cache	*array[NR_CPUS];
	/**
	 * 要转移进本地高速缓存或从本地高速缓存中转移出的大批对象的数量。
	 */
	unsigned int		batchcount;
	/**
	 * 本地高速缓存中空闲对象的最大数目。这个参数可调。
	 */
	unsigned int		limit;
/* 2) touched by every alloc & free from the backend */
	/**
	 * 包含三个链表,为什么要单独放到一个描述符中呢?
	 */
	struct kmem_list3	lists;
	/* NUMA: kmem_3list_t	*nodelists[MAX_NUMNODES] */
	/**
	 * 高速缓存中包含的对象的大小。
	 */
	unsigned int		objsize;
	/**
	 * 描述高速缓存永久属性的一组标志。
	 */
	unsigned int	 	flags;	/* constant flags */
	/**
	 * 在一个单独slab中的对象的个数。高速缓存中的所有slab具有相同的大小。
	 */
	unsigned int		num;	/* # of objs per slab */
	/**
	 * 整个slab高速缓存中空闲对象的上限。
	 */
	unsigned int		free_limit; /* upper limit of objects in the lists */
	/**
	 * 高速缓存自旋锁。
	 */
	spinlock_t		spinlock;

/* 3) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	/**
	 * 一个单独slab中包含的连续页框数目的对数。
	 */
	unsigned int		gfporder;

	/* force GFP flags, e.g. GFP_DMA */
	/**
	 * 分配页框时传递给伙伴系统函数的一组标志。
	 */
	unsigned int		gfpflags;

	/**
	 * slab使用的颜色个数。用于slab着色。
	 */
	size_t			colour;		/* cache colouring range */
	/**
	 * slab中的基本对齐偏移。
	 */
	unsigned int		colour_off;	/* colour offset */
	/**
	 * 下一个被分配的slab使用的颜色。就是对齐因子。
	 */
	unsigned int		colour_next;	/* cache colouring */
	/**
	 * 指向包含slab描述符的普通slab高速缓存。如果使用了内部slab描述符,则这个字段为NULL。
	 */
	kmem_cache_t		*slabp_cache;
	/**
	 * 单个slab的大小。
	 */
	unsigned int		slab_size;
	/**
	 * 高速缓存动态属性标志。
	 */
	unsigned int		dflags;		/* dynamic flags */

	/* constructor func */
	/**
	 * 高速缓存相关的构造方法的指针。
	 */
	void (*ctor)(void *, kmem_cache_t *, unsigned long);

	/* de-constructor func */
	/**
	 * 高速缓存相关的析构方法的指针。
	 */
	void (*dtor)(void *, kmem_cache_t *, unsigned long);

/* 4) cache creation/removal */
	/**
	 * 高速缓存名称。
	 */
	const char		*name;
	/**
	 * 高速缓存链表。
	 */
	struct list_head	next;

/* 5) statistics */
#if STATS
	/**
	 * 统计信息
	 */
	unsigned long		num_active;
	unsigned long		num_allocations;
	unsigned long		high_mark;
	unsigned long		grown;
	unsigned long		reaped;
	unsigned long 		errors;
	unsigned long		max_freeable;
	unsigned long		node_allocs;
	atomic_t		allochit;
	atomic_t		allocmiss;
	atomic_t		freehit;
	atomic_t		freemiss;
#endif
#if DEBUG
	/**
	 * 调试信息
	 */
	int			dbghead;
	int			reallen
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值