内存管理器(十三)kernel内存管理---slab的设计与实现1
前言
前面分析了kmalloc/free 函数,本来打算直接进入物理内存的管理,但是发现将这里作为突破口研究内存管理并不合适,由因为之前分析了Glibc库的内存管理,所以直接将这里作为入口深入进内存管理模块。
kernrl的学习路程就边学边探索,提升的是个人的学习能力。有了学历能力,就什么也不怕了。
__start
slab 可以说是一个对象也可以说是一个层次,其目的在于快速管理那些较小的分配对象,入购每一都使用页分配的伙伴算法其效率可想而知,内存中的很多数据结构他们大小并不大但是需要频繁的分配与释放,比如task_struct (进程描述符),struct inode(索引节点信息,文件IO 相关),等等只要是内核频繁访问的数据结构都有可能被作为一个类来建立一个slab(高速缓存组)。
它的编程实现还是使用了空闲链表,和我们之前分析的glibc malloc 原理类似,但是再内核中并没有实现过,这个最早的实现是再Sun OS 5.4 操作系统中实现的。linux 的slab 也是学习sun OS 的,(名字都没有变)。对于这种设计有几个设计的基本思想。
如果分配器知道对象的大小,页大小和总的高速缓存的大小这样的概念,应当做出更明智的决策。如果让部分的缓存专属于单个的处理器(对系统上每一个处理器独立而唯一),那么,分配和释放就可以在不加SMP锁的情况下进行。
如果分配器是NUMA 相关的,它就可以从相同的内存节点为请求者分配。
对存放的对象进行着色,以防止多个对象映射到相同的高速缓存行。
在Linux的slab 设计中考虑到了以上的几点。
slab层的设计
slab 层把不同的对象划分为高速缓存组,每一个高速缓存组都存放着不同类型的对象。每种对象类型对应一个高速缓存。例如一个高速缓存用来存放进程描述符,另一个高速缓存存放索引节点。简而言之,各个缓存管理的对象,会合并为较大的组,覆盖一个或者多个连续的页帧,这种组称之为slab,每个缓存由几个这种slab组成。比如我们使用的kmalloc( )就建立在slab 层之上,使用了一组通用的高速缓存。
个人感觉这里还是时用了面向对象的思想,例如各种slab就像是一种类,而不同类型的缓存就是对象,kmalloc()使用的就是一组通用的高速缓存。slab 由一个或者多个物理上连续的页组成,一般情况下一个页大小。每个高速缓存可以由多个slab组成。
每一个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构,每个slab 都是三种状态之一,满,部分满,空。
满:没有一个空闲的对象,都已经被分配。
部分满:一部分被分配,还有空余。
空:没有分配任何的对象,全部空闲。
当系统需要一个新的对象时候,先从部分满的slab中分配,如果没有部分满的slab ,就从空的slab中分配,如果没有一个空的slab就只能创建一个slab了。
这里多说一句,虽然slab分配器在很多方面都是很棒的,但是“人无完人,金无赤足。”,在配备有大量物理内存的大规模并行系统中,slab 所需要的大量元素数据就会变成一个问题,仅仅就slab的数据结构就会占用很多的内存,所以诞生了另外两个备选的分配器。
slob:这个分配器,一个轻量级的分配器。
slub:通过将页帧打包为组,试图最小化所须开销,不过事实证明它是对的。
一般默认的选择是slab ,但是不管他们谁,所有的接口是相同的,因为目的是相同的。
顺便符上一个内存体系简明构图:
下面是一个高速缓存,slab,以及对象之间的关系
如图所示,一个个的对象在slab 中,slab 在高速缓存中。搞清楚关系后接着进入下一步,看清这几种结构的数据结构,然后进入他们的函数接口,最终看清实现,领会思想,运用所学。还是我们的老套路。
数据结构
先看kmem_cache
一个高速缓存中可以含有多个kmem_cache对应的高速缓存,就拿L1高速缓存来举例,一个L1高速缓存对应 |
一个kmem_cache链表,这个链表中的任何一个kmem_cache类型的结构体均描述一个高速缓存,而这些高 |
速缓存在L1 cache中各自占用着不同的区域 |
这为几句话已经清除的将明白了高速缓存的数据结构是用来做什么的。
首先给出这个2.6内核的数据结构(来自互联网)。但是今天的重点是研究4.0.4内核的代码,毕竟使劲看7,8年前的内核总不是个事。
[c]
1、kmem_cache
高速缓存描述符:kmem_cache(管理一个高速缓存)
struct kmem_cache {
struct array_cache *array[NR_CPUS];
unsigned int batchcount; //要转移本地高速缓存的大批对象的数量
unsigned int limit; //本地高速缓存中空闲对象的最大数目
unsigned int shared;
unsigned int buffer_size; //高速缓存的大小
reciprocal_buffer_size;
unsigned int flags; //描述高速缓存永久属性的一组标志
unsigned int num; //封装在一个单独slab中的对象个数
unsigned int gfporder; // 一个单独slab中包含的连续页框数目的对数
gfp_t gfpflags;
size_t colour; //slab使用的颜色个数
unsigned_int colour_off; //slab中的基本对齐偏移
struct kmem_cache *slabp_cache;
unsigned int slab_size; //slab的大小
unsigned int dflags; //动态标志
void (*ctor)(void *,struct kmem_cache *,unsigned long); //构造函数
const char *name; //存放高速缓存名字的字符数组
struct list_head next; //高速缓存描述符双向链表使用的指针
struct kmem_list3 *nodelists[MAX_NUMNODES];//高速缓存中的slab链表
//下面三个参数待定
unsigned int objsize; //高速缓存中包含的对象的大小
unsigned int free_limit;//整个slab高速缓存中空闲对象的上限
spinlock_t spinlock;//高速缓存自旋锁
}
2、kmem_list3
高速缓存中的slab链表:kmem_list3
所有对象所依靠的slab链表
[c]
kmem_list3 {
struct list_head slabs_partial; //含有部分对象的链表
struct list_head slabs_full; //满对象链表
struct list_head slabs_free; //空对象链表
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; //每节点高速缓存的颜色
spinlock_t list_lock;
struct array_cache *shared; //每节点的共享高速缓存
struct array_cache **alien; //其他节点
unsigned long next_reap;
int free_touched;
struct kmem_list3 __initdata initkmem_list3[NUM_INIT_LISTS]
[/c]
这个是比较新的4.0.4内核代码的数据结构
[c]
/*
* Definitions unique to the original Linux SLAB allocator.
*/
struct kmem_cache {
struct array_cache __percpu *cpu_cache; //一个指针,指向一个数组,包含了与系统CPU数目相同的项数,具体见下文,对这个结构有剖析
/* 1) Cache tunables. Protected by slab_mutex */ /*几个可调参数,由slab_mutex 保护*/
unsigned int batchcount; /* 在L1缓存列表为空的情况下,从缓存slab 获取的对象个数*/
unsigned int limit; /*L1缓存per_cpu 缓存的最大对象个数,超过就返回batchcount 个*/
unsigned int shared; /*共享数目*/
unsigned int size; /*缓存中管理的对象的长度*/
struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */ /*每次分配与释放都会修改*/
unsigned int flags; /* constant flags */ /*常数标记*/
unsigned int num; /* # of objs per slab */ /*每个slab 中对象的个数*/
/* 3) cache_grow/shrink */ /*缓存增长与缩减*/
/* order of pgs per slab (2^n) */
unsigned int gfporder; /*每个slab 中页数*/
/* force GFP flags, e.g. GFP_DMA */
gfp_t allocflags; /*强制标志*/
size_t colour; /* cache colouring range */ /*着色范围*/
unsigned int colour_off; /* colour offset */ /*着色偏移*/
struct kmem_cache *freelist_cache;
unsigned int freelist_size;
/* constructor func */
void (*ctor)(void *obj); /*构造函数*/
/* 4) cache creation/removal */ /*缓存的创建与删除*/
const char *name; /*缓存名称*/
struct list_head list;
int refcount; /*使用计数*/
int object_size; /*对象长度*/
int align; /*对齐字数*/
/* 5) statistics */ /*静态调试常量,用作统计*/
#ifdef CONFIG_DEBUG_SLAB
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;
unsigned long node_frees;
unsigned long node_overflow;
atomic_t allochit;
atomic_t allocmiss;
atomic_t freehit;
atomic_t freemiss;
/*
* If debugging is enabled, then the allocator can add additional
* fields and/or padding to every object. size contains the total
* object size including these internal fields, the following two
* variables contain the offset to the user object and its size.
*/
int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEM
struct memcg_cache_params memcg_params;
#endif
struct kmem_cache_node *node[MAX_NUMNODES]; /*这个结构体比较重要,见下文*/
};
[/c]
[c]
#ifndef CONFIG_SLOB /*条件编译,这里和SLOB没关系不管*/
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock; /*一个保护的自旋锁*/
#ifdef CONFIG_SLAB /*这是我们分析的重点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; /*所有slab上容许最大的空闲个数*/
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
};
[/c]
[c]
最后再说下高速缓存的第一个指针数组结构体,这个结构体主要是对于多处理器适用,这里数组的项数和我们CPU的数量是相同的,主要记录了CPU的相关信息.
/*
* 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 {
unsigned int avail; /*当前可用的对象的数目。*/
unsigned int limit; /*指定了per-CPU列表中保存的对象的最大数目*/
unsigned int batchcount; /*指定了per-CPU列表为空的情况下,从缓存的slab中获取对象的数目*/
unsigned int touched; /*管理重要标识:移除一个对象时设置为1,而缓存收缩时设置为0*/
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
*/
/*至于最后一个数组元素,大家也可以看到balbalbalab..说了一堆,其实就是为了访问方便而设置的,
像这样 touched <==> (int *)entry[-1] 你们就说6不6?*/
};
[/c]
我只想说就紧紧是设计已经很复杂了,最后画一张图来看看。
OK!Done!