本文共 10554 字,大约阅读时间需要 35 分钟。
比如内核用伙伴系统为一个128字节的obj分配内存,内核不得不分配一个4k的page来存储该object,该页剩余的3972字节将会被闲置,内存资源被浪费。而且伙伴系统在释放一个内存块后,会立即将该内存块放在对应的freelist上,且该内存块还有可能与freelist上的相邻内存块合并成高一阶的内存块,若此时内核马上又要使用该内存块,则还需要再次从伙伴系统的freelist上去申请对应内存块,这种操作效率是很低的(未合理使用cpu高速缓存)。
为解决上面小块内存分配时遇到的问题,linux内核提出了slab分配机制,该内存分配器不是按页进行内存分配,而是按字节来分配的。
slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象放到slab缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分配器的slab缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分配器,内核将花费更多的时间去分配、初始化以及释放一个对象。
slab分配器把对象分组放进slab缓存。每个slab缓存都是同种类型对象的一种“储备”。一个slab cache管理一组大小固定的内存块(也称为对象实体),每个内存块都可用作一种数据结构。slab cache中的内存块来自一到多个slab。一个slab来自物理内存管理器的一到多个物理页,该slab被分成一组固定大小的块,被称为slab对象(object),一个slab属于一个slab cache,其中的对象就是该slab cache所管理的固定大小的内存块。所以一个slab cache可以有一到多个slab。
在基于slab的内核内存管理器中,基本的概念是保存管理型数据的缓存(即slab cache)和保存被管理对象的各个slab。每个缓存都负责一种对象类型,比如kmalloc-128缓存负责管理65-128字节内存空间的的分配。系统中的所有缓存类型都保存在一个链表slab_caches中。下图给出了slab分配器的各个组成部分间的相互关系:
slab分配器有以下三个基本目标:
在slab分配器中slab cache的描述符为struct kmem_cache,它是该机制的核心数据结构。
在slab分配器中,最顶层结构为slab cache,其描述符为struct kmem_cache.linux中每个slab obj对应的slab cache都有自己的名字(如:ext4_inode_cache,kmalloc-8和kmalloc-16等).
struct kmem_cache用于描述一种slab cache,并管理着一个或多个该slab中的的所有obj。linux中所有的kmem_cache会保持在一个以slab_caches作为头节点的链表中。kmem_cache代码定义如下所示。
// mm_slab_def.h/* * Definitions unique to the original Linux SLAB allocator. */struct kmem_cache { //本地高速缓存,每CPU结构,对象释放时,优先放入这个本地CPU高速缓存中 struct array_cache __percpu *cpu_cache;/* 1) Cache tunables. Protected by slab_mutex */ /*batchcount指定了在per-CPU变量中entry列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在 *缓存增长时,若entry数组中obj数量超过limit限制后,需要从entry数组中迁移走的obj数量(到本地节点cpu共享空闲链 *表中)。 */ unsigned int batchcount; //本地高速缓存中entry数组中空闲obj的最大数目 unsigned int limit; //CPU共享高速缓存标志,实际地址保存在kmem_cache_node结构中 unsigned int shared; //对象长度 + 填充字节 unsigned int size; struct reciprocal_value reciprocal_buffer_size;/* 2) touched by every alloc & free from the backend */ //属性的标识,如果SLAB管理结构放在外部,则CFLAGS_OFF_SLAB置1 unsigned int flags; /* constant flags */ //每个slab中obj数量 unsigned int num; /* # of objs per slab *//* 3) cache_grow/shrink */ /* order of pgs per slab (2^n) */ //每个slab页块的阶(一个slab由2^gfporder个页构成) unsigned int gfporder; /* force GFP flags, e.g. GFP_DMA */ //从伙伴系统分配页,补足slab时,页分配的gfp码 gfp_t allocflags; //一个slab中有多少不同的cache line size_t colour; /* cache colouring range */ //一个cache colour的长度,和一个cache line的大小相同 unsigned int colour_off; /* colour offset */ /* *空闲对象链表放在slab外部时使用,管理用于slab对象管理结构中freelist成员的缓存,也就是又一个新缓存 */ struct kmem_cache *freelist_cache; //空闲对象链表的大小 unsigned int freelist_size; /* constructor func */ // 创建高速缓存时的构造函数指针,一半为null void (*ctor)(void *obj);/* 4) cache creation/removal */ //slab缓存名字 const char *name; //slab缓存描述符双向链表指针 struct list_head list; int refcount; //slab中每个obj的大小 int object_size; //obj对齐字节 int align;......... //slab节点链表组,对于NUMA系统中每个节点都会有一个struct kmem_cache_node数据结构 struct kmem_cache_node *node[MAX_NUMNODES];};
通过struct kmem_cache的代码实现可以看出,slab cache中所有slab的大小一致,由一个或多个连续页组成(通常为一个page,伙伴系统提供),每个slab中的obj大小和数量也是相同的。
slab cache描述符struct kmem_cache中除了相关的管理数据外,有两个很重要的成员:
struct array_cache __percpu *cpu_cache:
// mm_slab_def.h/* * struct array_cache * * Purpose: * - LIFO ordering, to hand out cache-warm objects from _alloc * - reduce the number of linked list operations * - reduce spinlock operations * * The limit is stored in the per-cpu structure to reduce the data cache * footprint. * */struct array_cache { //当前CPU上该slab可用对象数量 unsigned int avail; //最大对象数目,和kmem_cache中一样 unsigned int limit; /*同kmem_cache,batchcount指定了在per-CPU列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在 *缓存增长时,若entry数组中obj数量超过limit限制后,需要迁移entry中obj的数量(到本地节点cpu共享空闲链 *表中)。 */ unsigned int batchcount; /*在从缓存移除一个对象时,将touched设置为1,而缓存收缩时, 则将touched设置为0。这使得内核能够确认在缓 * 存上一次收缩之后是否被访问过,也是缓存重要性的一个标志。 */ unsigned int touched; /*最后一个成员entry是一个伪数组, 其中并没有数组项, 只是为了便于访问内存中array_cache实例之后缓存中的 *各个对象而已 */ void *entry[]; /* * Must have this definition in here for the proper * alignment of array_cache. Also simplifies accessing * the entries. */};
struct kmem_cache_node *node[MAX_NUMNODES]:
slab缓存会为不同的节点维护一个自己的slab链表,用来缓存和管理自己节点的slab obj,这通过kmem_cache中node数组成员来实现,node数组中的每个数组项都为其对应的节点分配了一个struct kmem_cache_node结构来。
struct kmem_cache_node结构定义的变量是一个每node变量。相比于struct array_cache定义的每cpu变量,kmem_cache_node管理的内存区域粒度更大,因为kmem_cache_node维护的对象是slab,而array_cache维护的对象是slab中的obj(一个kmem_cache可能包含一个或多个slab,而一个slab中可能包含一个或多个slab).
通过下面struct kmem_cache_node结构的代码实现我们来分析该结构体如何实现对本地节点指定slab cache中所有的slab进行管理的。
//mm/slab.hstruct kmem_cache_node { //该链表中存储的所有slab中只有部分obj是空闲的 struct list_head slabs_partial; /* partial list first, better asm code */ //该链表中存储的所有slab中不存在空闲的obj struct list_head slabs_full; //该链表中存储的所有slab中每个obj都是空闲的 struct list_head slabs_free; //该节点中此kmem_cache的slab总数 unsigned long num_slabs; //该节点中此kmem_cache空闲obj总数 unsigned long free_objects; //该节点中此kmem_cache中空闲obj数量的上限,多了就会回收到伙伴系统的空闲链表中 unsigned int free_limit; unsigned int colour_next; /* Per-node cache coloring */ //该节点上所有cpu共享的本地高速缓存 struct array_cache *shared; /* shared per node */ //其他节点上所有cpu共享的本地高速缓存 struct alien_cache **alien; /* on other nodes */ unsigned long next_reap; /* updated without locking */ int free_touched; /* updated without locking */};
由上代码可以看出,struct kmem_cache_node对于本节点中slab的管理主要分了3个链表:slabs_partial(部分空闲slab链表),非空闲slab链表(slabs_full)和全空闲slab链表(slabs_free).struct kmem_cache_node还会将本地节点中需要节点共享的slab obj缓存在它的shared成员中。若本地节点向访问其他节点贡献的slab obj,可以利用struct kmem_cache_node中的alien成员去获取。
为了提高linux内存利用率,slab描述符结合在页描述符struct page中,当页描述符成员flage标志设置了PG_SLAB后,struct page中的部分成员可以用来描述slab对象.如下代码所示(由于struct page成员很多,下面只将描述slab对象有关的成员列出):
//为了节省内存,struct page中许多结构体会通过union联合体进行封装,此处做了省略struct page { /* First double word block */ //当页描述符成员flage标志设置了PG_SLAB后,struct page中对应的slab成员才有效 unsigned long flags; /* Atomic flags, some possibly updated asynchronously */ void *s_mem; /* slab first object */ //freelist实际上是一个数组,数组中存放slab中未使用的obj的index值 void *freelist; /* sl[aou]b first free object */ /* page_deferred_list().prev -- second tail page */ //当前slab已经使用的obj数量 unsigned int active; /* SLAB */ /* 通过该成员将链到slab cache对应节点kmem_cache_node所管理的3个链表上*/ struct list_head lru; /* Pageout list, eg. active_list * protected by zone_lru_lock ! * Can be used as a generic list * by the page owner. */ //指向slab的slab cache描述符kmem_cache struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */ //slab首地址,待验证 void *virtual; }
由上面代码可以看出在slab描述符中最重要两个数据结构是s_mem和freelist这两个成员。
下面介绍slab的空闲索引数组保存在slab自身内部的情况slab描述符是如何管理slab和其内部obj,如下图所示:
通过示意图来描述slab如何根据freelist数组和active成员来分配和释放其内部的空闲obj的(假设该slab中有6个obj)。
在slab机制中slab管理的内存区域的连续页块数,和slab cache结构描述符struct kmem_cache中的gfporder有关。该gfp是在slab cache初始化时通过每个slab中obj的数量和大小,着色区大小,freelist大小,slab对象描述符大小等一系列数据计算而来。需要注意的是这些对象的大小并不是创建的时候对象本身的大小,系统为了优化内存访问还会做一些字节对齐的填充,或者是在对象交界处填充一些magicnum进行防越界处理(如red_zone).
通过上图我们对slab分配器中各个重要结构体间的关系做一个总结:
slab机制中每个slab cache对应一个struct kmem_cache结构体,linux内核中会分配一个静态全局的struct kmem_cache变量slab_caches,该指向一个双向链表,内核中所有的slab cache的结构描述符都会挂到该链表中(通过list成员变量接入slab_caches链表)。我们可以在shell中断通过/proc/slabinfo查看内核中所有slab cache的相关数据信息。
/ # cat /proc/slabinfoslabinfo - version: 2.1# name: tunables : slabdata >zswap_entry 0 0 360 22 2 : tunables 0 0 0 : slabdata 0 0 0sw_flow_stats 0 0 448 18 2 : tunables 0 0 0 : slabdata 0 0 0 ...... ...... ......dma-kmalloc-64 0 0 368 22 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-32 0 0 336 24 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-16 0 0 320 25 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-8 0 0 312 26 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-192 0 0 496 16 2 : tunables 0 0 0 : slabdata 0 0 0dma-kmalloc-96 0 0 400 20 2 : tunables 0 0 0 : slabdata 0 0 0kmem_cache_node 213 216 448 18 2 : tunables 0 0 0 : slabdata 12 12 0kmem_cache 216 224 576 28 4 : tunables 0 0 0 : slabdata 8 8 0
每个slab cache的描述符(struct kmem_cache)中会包含一个Per-CPU结构struct array_cache,该结构体用来作为对应slab cache对象的本地CPU高速缓存,缓存的对象是slab obj.
每个slab cache的描述符中还包含了一个struct kmem_cache_node数组成员node:
slab的描述符共用了页的结构描述符struct page,此时struct page中的flage标志成员置位了PG_SLAB位。
每个slab cache包含了一个或多个slab,同一个slab cache中的slab指向的内存区域大小一样(2^gfporder),且slab内部包含的slab obj数量也一样。
slab描述符(struct page)中的s_mem成员指向了slab中第一个obj(存储obj的首地址),而freelist成员指向了slab的空闲obj索引数组,它结合描述符中的active成员就能获取第一个空闲obj在s_mem指向内存区域的索引。
slab着色区的作用是为了错开不同的slab,让CPU更有效的缓存slab。
(1).通过slab着色(slab coloring), slab分配器能够均匀地在缓存中分布对象, 以实现均匀的缓存利用(2).经常使用的内核对象保存在CPU高速缓存中,这是我们想要的效果。从`slab`分配器的角度进行衡量, 伙伴系统的高速缓存和TLB占用较大, 这是一个负面效应,因为这会导致不重要的数据驻留在`CPU`高速缓存中, 而重要的数据则被置换到内存, 显然应该防止这种情况出现.(3).着色这个术语是隐喻性的. 它与颜色无关, 只是表示slab中的对象需要移动的特定偏移量, 以便使对象放置到不同的缓存行.
转载地址:http://upbgi.baihongyu.com/