伙伴系统支持按页分配内存,但这个单位太大了。如果需要为一个10个字符的字符串分配空间,分配一个4 KiB或更多空间的完整页面,不仅浪费而且完全不可接受。显然的解决方案是将页拆分为更小的单位,可以容纳大量的小对象。
提供小内存块不是slab分配器的唯一任务。由于结构上的特点,它也用作一个缓存,主要针对经常分配并释放的对象。通过建立slab缓存,内核能够储备一些对象,供后续使用。
经常使用的内核对象保存在CPU高速缓存中(L1、L2缓存等),这是我们想要的效果。前文的注释提到从slab分配器的角度进行衡量,伙伴系统的高速缓存和TLB占用较大(因为伙伴系统的最少单页我4k),这是一个负面效应。因为这会导致不重要的数据驻留在CPU高速缓存中,而重要的数据则被置换到内存,显然应该防止这种情况出现。
1、伙伴系统的备选分配器
slab分配器所需的大量元数据可能成为一个问题:开发者称,在大型系统上仅slab的数据结构就需要很多吉字节内存。对嵌入式系统来说, slab分配器代码量和复杂性都太高。为处理此类情形,在内核版本2.6开发期间,增加了slab分配器的两个替代品。
1、slob分配器进行了特别优化,以便减少代码量。它围绕一个简单的内存块链表展开(因此而得名)。在分配内存时,使用了同样简单的最先适配算法。slob分配器只有大约600行代码,总的代码量很小。事实上,从速度来说,它不是最高效的分
配器,也肯定不是为大型系统设计的。
2、 slub分配器通过将页帧打包为组,并通过struct page中未使用的字段来管理这些组,试图最小化所需的内存开销。读者此前已经看到,这样做不会简化该结构的定义,但在大型计算机上slub比slab提供了更好的性能,说明了这样做是正确的。
2、内核中的内存管理
从程序员的角度来看,建立和使用缓存的任务不是特别困难。必须首先用kmem_cache_create建立一个适当的缓存,接下来即可使用kmem_cache_alloc和kmem_cache_free分配和释放其中包含的对象。 slab分配器负责完成与伙伴系统的交互,来分配所需的页。
所有活动缓存的列表保存在/proc/slabinfo中
wolfgang@meitner> cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables
<limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
nf_conntrack_expect 0 0 224 18 1 : tunables 0 0 0 : slabdata 0 0 0
UDPv6 16 16 960 4 1 : tunables 0 0 0 : slabdata 4 4 0
TCPv6 19 20 1792 4 2 : tunables 0 0 0 : slabdata 5 5 0
xfs_inode 25721 25725 576 7 1 : tunables 0 0 0 : slabdata 3675 3675 0
xfs_efi_item 44 44 352 11 1 : tunables 0 0 0 : slabdata 4 4 0
xfs_efd_item 44 44 360 11 1 : tunables 0 0 0 :
slabdata 4 4 0
...
kmalloc-128 795 992 128 32 1 : tunables 0 0 0 : slabdata 31 31 0
kmalloc-64 19469 19584 64 64 1 : tunables 0 0 0 : slabdata 306 306 0
kmalloc-32 2942 2944 32 128 1 : tunables 0 0 0 : slabdata 23 23 0
kmalloc-16 2869 3072 16 256 1 : tunables 0 0 0 : slabdata 12 12 0
kmalloc-8 4075 4096 8 512 1 : tunables 0 0 0 : slabdata 8 8 0
kmalloc-192 2940 3276 192 21 1 : tunables 0 0 0 : slabdata 156 156 0
kmalloc-96 754 798 96 42 1 : tunables 0 0 0 : slabdata 19 19 0
缓存中活动对象的数量。
缓存中对象的总数(已用和未用)。
所管理对象的长度,按字节计算。
一个slab中对象的数量。
每个slab中页的数量。
活动slab的数量。
在内核决定向缓存分配更多内存时,所分配对象的数量。每次会分配一个较大的内存块,以减少与伙伴系统的交互。在缩小缓存时,也使用该值作为释放内存块的大小。
每次调用kmalloc时,内核找到最适合的缓存,并从中分配一个对象满足请求(如果没有刚好适合的缓存,则分配稍大的对象,但不会分配更小的对象)
3、slab分配 原理
![](https://i-blog.csdnimg.cn/blog_migrate/008cb7232e1e61778932b96a13bf05e9.png)
缓存:只负责一种对象类型(例如kmalloc-32实例),或提供一般性的缓冲区。各个缓存中slab的数目各有不同,这与已经使用的页的数目、对象长度和被管理对象的数目有关。
另外,系统中所有的缓存都保存在一个双链表中。这使得内核有机会依次遍历所有的缓存
1、缓存的精细结构struct kmem_cache
除了管理性数据(如已用和空闲对象或标志寄存器的数目),缓存结构包括两个特别重要的成员。
- 指向一个数组的指针,其中保存了各个CPU最后释放的对象。
- 每个内存结点都对应3个表头,用于组织slab的链表。第1个链表包含完全用尽的slab,第2个是部分空闲的slab,第3个是空闲的slab。
缓存结构中包含了与系统CPU数目相同的数组项。每个元素都是一个指针,指向一个结构称之为数组缓存( array cache),其中包含了对应于特定系统CPU的管理数据。
为最好地利用CPU高速缓存,这些per-CPU指针是很重要的。在分配和释放对象时,采用后进先出原理( LIFO, last in first out)。内核假定刚释放的对象仍然处于CPU高速缓存中,会尽快再次分配它(响应下一个分配请求)。仅当per-CPU缓存为空时,才会用slab中的空闲对象重新填充它们。
注意上面的slab对象,每个slab都包含在页帧中,而空闲、部分空闲、用尽的链表就是将这些页帧链接起来。空闲的链表表示这个链表里的所有页帧中的所有对象都没被分配出去。z
slab分配的顺序:
对象分配的体系就形成了一个三级的层次结构,分配成本和操作对CPU高速缓存和TLB的负面影响逐级升高。
(1) 仍然处于CPU高速缓存中的per-CPU对象。
(2) 现存slab中未使用的对象。
(3) 刚使用伙伴系统分配的新slab中未使用的对象。
2、slab对象的精细结构
对象在slab中并非连续排列,而是按照一个相当复杂的方案分布。每个对象的长度并不反映其确切的大小。相反,长度已经进行了舍入,以满足某些对齐方式的要求。
填充字节可以加速对slab中对象的访问。如果使用对齐的地址,那么在几乎所有的体系结构上,内存访问都会更快。这弥补了使用填充字节必然导致需要更多内存的不利情况。
管理结构位于每个slab的起始处,保存了所有的管理数据(和用于连接缓存链表的链表元素)。其后面是一个数组,每个(整数)数组项对应于slab中的一个对象。只有在对象没有分配时,相应的数组项才有意义。在这种情况下,它指定了下一个空闲对象的索引。由于最低编号的空闲对象的编号还保存在slab起始处的管理结构中,内核无需使用链表或其他复杂的关联机制,即可轻松找到当前可用的所有对象。数组的最后一项总是一个结束标记,值为BUFCTL_END。
首先管理数组中没有分配的数组项才有意义,并且每个数组项中存放了下一个没有分配的数组项的索引。
array[1]=2
array[2]=4
array[4]=6
array[1]就是队列头,从这可以看出没有被分配的slab对象为1->2->4->6,那么0、3、5的slab已经被分配出去了,应了后面的图示。
设置或读取slab信息分别由set_page_slab和get_page_slab函数完成,带有_cache后缀的函数则处理缓存信息的设置和读取。
mm/slab.c
void page_set_cache(struct page *page, struct kmem_cache *cache)
struct kmem_cache *page_get_cache(struct page *page)
void page_set_slab(struct page *page, struct slab *slab)
struct slab *page_get_slab(struct page *page)
此外,内核还对分配给slab分配器的每个物理内存页都设置标志PG_SLAB。
4、实现
1、kmem_cache_create函数:
先看看缓存的数据结构
/* slab分配器中的SLAB高速缓存 */
struct kmem_cache {
/* 指向包含空闲对象的本地高速缓存,每个CPU有一个该结构,当有对象释放时,优先放入本地CPU高速缓存中 */
struct array_cache __percpu *cpu_cache;
/* 1) Cache tunables. Protected by slab_mutex */
/* 要转移进本地高速缓存或从本地高速缓存中转移出去的对象的数量 */
unsigned int batchcount;
/* 本地高速缓存中空闲对象的最大数目 */
unsigned int limit;
/* 是否存在CPU共享高速缓存,CPU共享高速缓存指针保存在kmem_cache_node结构中 */
unsigned int shared;
/* 对齐后对象长度 */
unsigned int size;
/* size的倒数,加快计算 */
struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
/* 高速缓存永久属性的标识,如果SLAB描述符放在外部(不放在SLAB中),则CFLAGS_OFF_SLAB置1 */
unsigned int flags; /* constant flags */
/* 每个SLAB中对象的个数(在同一个高速缓存中slab中对象个数相同) */
unsigned int num; /* # of objs per slab */
/* 3) cache_grow/shrink */
/* 一个单独SLAB中包含的连续页框数目的对数 */
unsigned int gfporder;
/* 分配页框时传递给伙伴系统的一组标识 */
gfp_t allocflags;
/* SLAB使用的颜色个数 */
size_t colour;
/* SLAB中基本对齐偏移,当新SLAB着色时,偏移量的值需要乘上这个基本对齐偏移量,理解就是1个偏移量等于多少个B大小的值 */
unsigned int colour_off;
/* 空闲对象链表放在外部时使用,其指向的SLAB高速缓存来存储空闲对象链表 */
struct kmem_cache *freelist_cache;
/* 空闲对象链表的大小(对象个数对齐之后的值) */
unsigned int freelist_size;
/* 构造函数,一般用于初始化这个SLAB高速缓存中的对象 */
void (*ctor)(void *obj);
/* 4) cache creation/removal */
/* 存放高速缓存名字 */
const char *name;
/* 高速缓存描述符双向链表指针 */
struct list_head list;
int refcount;
/* 实际申请的对象大小 */
int object_size;
int align;
#ifdef CONFIG_MEMCG_KMEM
/* 用于分组资源限制 */
struct memcg_cache_params *memcg_params;
#endif
/* 结点链表,此高速缓存可能在不同NUMA的结点都有SLAB链表 */
struct kmem_cache_node *node[MAX_NUMNODES];
};
每cpu缓存:
struct array_cache {
unsigned int avail;/*当前cpu上有多少个可用的对象*/
unsigned int limit;/*per_cpu里面最大的对象的个数,当超过这个值时,将对象返回给伙伴系统*/
unsigned int batchcount;/*一次转入和转出的对象数量*/
/*啥叫一次转入和转出的对象数量呢?
转入即 当array_cache里面没有数据的时间,即avail为0的时候。array_cache需要从cpu共享高速缓存内读取多少个对象内容到cpu_cache补充,再如果cpu共享高速缓存里面没有的话就去。
转出指本地高速缓存的空间已满,则按batchcount的值将batchcount个对象从本地高速缓存转移出去.
(ps : 下面再具体的说内容)
*/
unsigned int touched;/*标示本地cpu最近是否被使用*/
spinlock_t lock;/*自旋锁*/
void *entry[]; /* * Must have this definition in here for the proper * alignment of array_cache. Also simplifies accessing * the entries. */
//entry指向对象的起始地址
};
————————————————
版权声明:本文为CSDN博主「OnePunch-Man」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013837209/article/details/58205678
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
};
/*
name:对象名称
size:对象大小
align:对齐长度,一般传0
flags:一些分配申请的标记
*/
struct kmem_cache *kmem_cache_create(const char *name, size_t size,size_t align,long falgs)
{
struct kmem_cache *s = NULL;
const char *cache_name;
int err;
mutex_lock(&slab_mutex);
cache_name = kstrdup_const(name, GFP_KERNEL);
if (!cache_name) {
err = -ENOMEM;
goto out_unlock;
}
s = create_cache(cache_name, size, size,calculate_alignment(flags, align, size),flags, ctor, NULL, NULL);
if (IS_ERR(s)) {
err = PTR_ERR(s);
kfree_const(cache_name);
}
out_unlock:
mutex_unlock(&slab_mutex);
return s;
}
calculate_alignment是算对其的大小,如果align是0的话,返回值为8.
create_cache函数:
static struct kmem_cache *create_cache(const char *name,
size_t object_size, size_t size, size_t align,
unsigned long flags,
struct mem_cgroup *memcg, struct kmem_cache *root_cache)
{
struct kmem_cache *s;
int err;
err = -ENOMEM;
/* 申请slab缓存空间 */
s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
s->name = name;
s->object_size = object_size;
s->size = size;
s->align = align;
s->ctor = ctor;
err = __kmem_cache_create(s, flags);
s->refcount = 1;
list_add(&s->list, &slab_caches);
return s;
}
__kmem_cache_create函数:
int
__kmem_cache_create (struct kmem_cache *cachep, unsigned long flags)
{
size_t left_over, freelist_size;
size_t ralign = BYTES_PER_WORD;
gfp_t gfp;
int err;
size_t size = cachep->size;
/*
* BYTES_PER_WORD = 8,这在算字节对其,如果申请的size大小是126,对齐后size=128
*/
if (size & (BYTES_PER_WORD - 1)) {
size += (BYTES_PER_WORD - 1);
size &= ~(BYTES_PER_WORD - 1);
}
/*将对其的字节长度存储*/
cachep->align = ralign;
/* 决定slab管理结构是否在slab内部
* Determine if the slab management is 'on' or 'off' slab.
* (bootstrapping cannot cope with offslab caches so don't do
* it too early on. Always use on-slab management when
* SLAB_NOLEAKTRACE to avoid recursive calls into kmemleak)
*/
if (size >= OFF_SLAB_MIN_SIZE && !slab_early_init &&
!(flags & SLAB_NOLEAKTRACE))
/*
* Size is large, assume best to place the slab management obj
* off-slab (should allow better packing of objs).
*/
flags |= CFLGS_OFF_SLAB;
/* 再次对齐 */
size = ALIGN(size, cachep->align);
/*计算需要申请多少页帧,这些页帧可以包含多少个对象,并返回除去管理结构、每个slab对象后的剩余内存 */
left_over = calculate_slab_order(cachep, size, cachep->align, flags);
/* 缓存对象的管理结构大小,如下解释所示,对其后为32 */
freelist_size = calculate_freelist_size(cachep->num, cachep->align);
/*
* If the slab has been placed off-slab, and we have enough space then
* move it on-slab. This is at the expense of any extra colouring.
*/
if (flags & CFLGS_OFF_SLAB && left_over >= freelist_size) {
flags &= ~CFLGS_OFF_SLAB;
left_over -= freelist_size;
}
/* 硬件告诉缓存一行大小,比如64B */
cachep->colour_off = cache_line_size();
/* 127/64=1 */
cachep->colour = left_over / cachep->colour_off;
/* 管理结构大小,已对其,32B */
cachep->freelist_size = freelist_size;
/* 分配标记 */
cachep->flags = flags;
cachep->allocflags = __GFP_COMP;
if (CONFIG_ZONE_DMA_FLAG && (flags & SLAB_CACHE_DMA))
cachep->allocflags |= GFP_DMA;
/* 已对其后的缓存对象大小 */
cachep->size = size;
cachep->reciprocal_buffer_size = reciprocal_value(size);
/* 如果管理结构在slab外部,那么就需要单独给管理结构申请内存。此处暂时不考虑这种情况 */
if (flags & CFLGS_OFF_SLAB) {
cachep->freelist_cache = kmalloc_slab(freelist_size, 0u);
BUG_ON(ZERO_OR_NULL_PTR(cachep->freelist_cache));
}
/* 初始化每CPU缓存,array_cache,这是一个数组,每个cpu对应其中一个元素 */
err = setup_cpu_cache(cachep, gfp);
if (err) {
__kmem_cache_shutdown(cachep);
return err;
}
return 0;
}
static size_t calculate_slab_order(struct kmem_cache *cachep,
size_t size, size_t align, unsigned long flags)
{
unsigned long offslab_limit;
size_t left_over = 0;
int gfporder;
for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
unsigned int num;
size_t remainder;
//num:一个slab可以包含多少对象;remainder:4096-所有对象大小-管理结构大小。具体如下
cache_estimate(gfporder, size, align, flags, &remainder, &num);
if (!num)
continue;
cachep->num = num;
cachep->gfporder = gfporder;
left_over = remainder;
}
return left_over;
}
static int __init_refok setup_cpu_cache(struct kmem_cache *cachep, gfp_t gfp)
{
/* 给每cpu缓存申请内存,并将每个的limit和batchcount赋值为1 */
/*
if (ac) {
ac->avail = 0;
ac->limit = limit;
ac->batchcount = batch;
ac->touched = 0;
}
*/
cachep->cpu_cache = alloc_kmem_cache_cpus(cachep, 1, 1);
if (!cachep->cpu_cache)
return 1;
if (slab_state == DOWN) {
/* Creation of first cache (kmem_cache). */
set_up_node(kmem_cache, CACHE_CACHE);
} else if (slab_state == PARTIAL) {
/* For kmem_cache_node */
set_up_node(cachep, SIZE_NODE);
} else {
int node;
/* 如果有多节点,给每个结点申请node内存 */
for_each_online_node(node) {
cachep->node[node] = kmalloc_node(
sizeof(struct kmem_cache_node), gfp, node);
BUG_ON(!cachep->node[node]);
/* 给没个node中的队列和所有字段都初始化为0 */
kmem_cache_node_init(cachep->node[node]);
}
}
cachep->node[numa_mem_id()]->next_reap =
jiffies + REAPTIMEOUT_NODE +
((unsigned long)cachep) % REAPTIMEOUT_NODE;
/* 以下又给当前cpu缓存赋值,与上面没有区别 */
cpu_cache_get(cachep)->avail = 0;
cpu_cache_get(cachep)->limit = BOOT_CPUCACHE_ENTRIES;//1
cpu_cache_get(cachep)->batchcount = 1;
cpu_cache_get(cachep)->touched = 0;
/* 给kmem_cache自己的字段赋值 */
cachep->batchcount = 1;
cachep->limit = BOOT_CPUCACHE_ENTRIES;//1
return 0;
}
static void kmem_cache_node_init(struct kmem_cache_node *parent)
{
INIT_LIST_HEAD(&parent->slabs_full);
INIT_LIST_HEAD(&parent->slabs_partial);
INIT_LIST_HEAD(&parent->slabs_free);
parent->total_slabs = 0;
parent->free_slabs = 0;
parent->shared = NULL;
parent->alien = NULL;
parent->colour_next = 0;
spin_lock_init(&parent->list_lock);
parent->free_objects = 0;
parent->free_touched = 0;
}
calculate_slab_order函数,比如我们用128(其实我们是申请的127,这是对齐后的结果)这个对象来计算。cache_estimate函数在计算,order=0就可以满足。
order=0(4096B)
num = 4096/(slab缓存对象大小(128B)+ 管理结构(1B)) 也就是31
总的管理结构freelist_size=align(31*1)=32
left_over=remainder = 4096-31*128-32=96
综上,__kmem_cache_create函数就是申请一个缓存,并对缓存中的数据进行初始化,整理所初始化的字段:
cachep->align = ralign;
cachep->num = num;
cachep->gfporder = gfporder;
/* 硬件告诉缓存一行大小,比如64B */
cachep->colour_off = cache_line_size();
/* 127/64=1 */
cachep->colour = left_over / cachep->colour_off;
/* 管理结构大小,已对其,32B */
cachep->freelist_size = freelist_size;
/* 分配标记 */
cachep->flags = flags;
cachep->allocflags = __GFP_COMP;
/* 已对其后的缓存对象大小 */
cachep->size = size;
cachep->reciprocal_buffer_size;
cachep->batchcount = 1;
cachep->limit = BOOT_CPUCACHE_ENTRIES;//1
/* 一下又给每cpu缓存赋值,与上面没有区别 */
cpu_cache_get(cachep)->avail = 0;
cpu_cache_get(cachep)->limit = BOOT_CPUCACHE_ENTRIES;//1
cpu_cache_get(cachep)->batchcount = 1;
cpu_cache_get(cachep)->touched = 0;
/* 给kmem_cache自己的字段赋值 */
所有kmeme_cache_node的东西全赋值为0
这个函数并没有申请内存,只是进行初始化,比如申请的大小需要多少个页帧,可以包含多少个缓存对象,对管理结构的初始化,每cpu缓存的初始化和最后一个kmem_cache_node的初始化等等。分配的工作交给了另一个函数进行,下面说一下。
2、slab申请
slab_alloc()->__do_cache_alloc()->__cache_alloc()
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
bool force_refill = false;
check_irq_off();
ac = cpu_cache_get(cachep);
//如果cpu缓存中有的话直接从缓存中拿
if (likely(ac->avail)) {
ac->touched = 1;
objp = ac_get_obj(cachep, ac, flags, false);
if (objp) {
STATS_INC_ALLOCHIT(cachep);
goto out;
}
force_refill = true;
}
STATS_INC_ALLOCMISS(cachep);
//cpu缓存的中的slab对象全被分配出去后,需要对cpu缓存重新填充
objp = cache_alloc_refill(cachep, flags, force_refill);
ac = cpu_cache_get(cachep);
out:
/*
* To avoid a false negative, if an object that is in one of the
* per-CPU caches is leaked, we need to make sure kmemleak doesn't
* treat the array pointers as a reference to the object.
*/
if (objp)
kmemleak_erase(&ac->entry[ac->avail]);
return objp;
}
static inline void *ac_get_obj(struct kmem_cache *cachep,
struct array_cache *ac, gfp_t flags, bool force_refill)
{
void *objp;
objp = ac->entry[--ac->avail];
return objp;
}
struct page {
/* First double word block */
/* 用于页描述符,一组标志(如PG_locked、PG_error),也对页框所在的管理区和node进行编号 */
unsigned long flags; /
union {
/* 用于页描述符,当页被插入页高速缓存中时使用,或者当页属于匿名区时使用 */
struct address_space *mapping;
/* 用于SLAB描述符,指向第一个对象的地址 */
void *s_mem; /* slab first object */
};
/* Second double word */
struct {
union {
/* 作为不同的含义被几种内核成分使用。例如,它在页磁盘映像或匿名区中标识存放在页框中的数据的位置,或者它存放一个换出页标识符 */
pgoff_t index; /* Our offset within mapping. */
/* 用于SLAB描述符,指向空闲对象链表 */
void *freelist;
/* 当管理区页框分配器压力过大时,设置这个标志就确保这个页框专门用于释放其他页框时使用 */
bool pfmemalloc;
};
union {
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
/* Used for cmpxchg_double in slub */
/* SLUB使用 */
unsigned long counters;
#else
/* SLUB使用 */
unsigned counters;
#endif
struct {
union {
/* 页框中的页表项计数,如果没有为-1,如果为PAGE_BUDDY_MAPCOUNT_VALUE(-128),说明此页及其后的一共2的private次方个数页框处于伙伴系统中,正在使用时应该是0 */
atomic_t _mapcount;
struct { /* SLUB使用 */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
int units; /* SLOB */
};
/* 页框的引用计数,如果为-1,则此页框空闲,并可分配给任一进程或内核;如果大于或等于0,则说明页框被分配给了一个或多个进程,或用于存放内核数据。page_count()返回_count加1的值,也就是该页的使用者数目 */
atomic_t _count; /* Usage count, see below. */
};
/* 用于SLAB时描述当前SLAB已经使用的对象 */
unsigned int active; /* SLAB */
};
};
/* Third double word block */
union {
/* 包含到页的最近最少使用(LRU)双向链表的指针,用于插入伙伴系统的空闲链表中,只有块中头页框要被插入。也用于SLAB,加入到kmem_cache中的SLAB链表中 */
struct list_head lru;
/* SLAB使用 */
struct { /* slub per cpu partial pages */
struct page *next; /* Next partial slab */
#ifdef CONFIG_64BIT
int pages; /* Nr of partial slabs left */
int pobjects; /* Approximate # of objects */
#else
short int pages;
short int pobjects;
#endif
};
/* SLAB使用 */
struct slab *slab_page; /* slab fields */
struct rcu_head rcu_head; /* Used by SLAB
* when destroying via RCU
*/
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS
pgtable_t pmd_huge_pte; /* protected by page->ptl */
#endif
};
/* Remainder is not double word aligned */
union {
/* 可用于正在使用页的内核成分(例如: 在缓冲页的情况下它是一个缓冲器头指针,如果页是空闲的,则该字段由伙伴系统使用,在给伙伴系统使用时,表明的是块的2的次方数,只有块的第一个页框会使用) */
unsigned long private;
#if USE_SPLIT_PTE_PTLOCKS
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
#endif
/* SLAB描述符使用,指向SLAB的高速缓存 */
struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */
struct page *first_page; /* Compound tail pages */
};
#if defined(WANT_PAGE_VIRTUAL)
/* 线性地址,如果是没有映射的高端内存的页框,则为空 */
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
unsigned long debug_flags; /* Use atomic bitops on this */
#endif
#ifdef CONFIG_KMEMCHECK
void *shadow;
#endif
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
}
对于分配函数,绕过开始,先看填充函数cache_alloc_refill():
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
int batchcount;
struct kmem_cache_node *n;
struct array_cache *ac, *shared;
int node;
void *list = NULL;
struct page *page;
check_irq_off();
node = numa_mem_id();
ac = cpu_cache_get(cachep);
//计算一次向缓存添加的数目
batchcount = ac->batchcount;
if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
/*
* If there was little recent activity on this cache, then
* perform only a partial refill. Otherwise we could generate
* refill bouncing.
*/
batchcount = BATCHREFILL_LIMIT;
}
//找到当前节点的管理结构(也是一个缓存)
n = get_node(cachep, node);
BUG_ON(ac->avail > 0 || !n);
shared = READ_ONCE(n->shared);
//如果可用slab为空的直接增长
if (!n->free_objects && (!shared || !shared->avail))
goto direct_grow;
spin_lock(&n->list_lock);
shared = READ_ONCE(n->shared);
while (batchcount > 0) {
/* 找到还包含可用slab的页,可能是部分满的页也可能是空的页,函数在下面 */
page = get_first_slab(n, false);
//如果没有部分满的页同时没有空页那么直接增长
if (!page)
goto must_grow;
batchcount = alloc_block(cachep, ac, page, batchcount);
fixup_slab_list(cachep, n, page, &list);
}
must_grow:
//能进这个函数avail=0,因为不为0的话在父函数就直接从cpu缓存中去slab了
n->free_objects -= ac->avail;
alloc_done:
spin_unlock(&n->list_lock);
fixup_objfreelist_debug(cachep, &list);
direct_grow:
if (unlikely(!ac->avail)) {
//获取一个页帧
page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
/*
* cache_grow_begin() can reenable interrupts,
* then ac could change.
*/
//获取当前的cpu cache
ac = cpu_cache_get(cachep);
//此时avail=0,page已申请成功
//alloc_block中心函数,从已申请的page页帧中,取出batchcount个slab放入到cpu 的cache中
if (!ac->avail && page)
alloc_block(cachep, ac, page, batchcount);
cache_grow_end(cachep, page);
if (!ac->avail)
return NULL;
}
ac->touched = 1;
return ac->entry[--ac->avail];
}
static struct page *get_first_slab(struct kmem_cache_node *n, bool pfmemalloc)
{
struct page *page;
assert_spin_locked(&n->list_lock);
page = list_first_entry_or_null(&n->slabs_partial, struct page, lru);
if (!page) {
n->free_touched = 1;
page = list_first_entry_or_null(&n->slabs_free, struct page,
lru);
if (page)
n->free_slabs--;
}
if (sk_memalloc_socks())
page = get_valid_first_slab(n, page, pfmemalloc);
return page;
}
static struct page *cache_grow_begin(struct kmem_cache *cachep,
gfp_t flags, int nodeid)
{
void *freelist;
size_t offset;
gfp_t local_flags;
int page_node;
struct kmem_cache_node *n;
struct page *page;
local_flags = flags & (GFP_CONSTRAINT_MASK|GFP_RECLAIM_MASK);
check_irq_off();
/*
* Get mem for the objs. Attempt to allocate a physical page from
* 'nodeid'.
*/
//此函数不多看了,从伙伴系统申请一个页帧
page = kmem_getpages(cachep, local_flags, nodeid);
if (!page)
goto failed;
//n是本内存结点的kmem_cache_node结构
page_node = page_to_nid(page);
n = get_node(cachep, page_node);
/* Get colour for the slab, and cal the next value. */
n->colour_next++;
if (n->colour_next >= cachep->colour)
n->colour_next = 0;
offset = n->colour_next;
if (offset >= cachep->colour)
offset = 0;
//根据上面申请128字节的cache来说。这里offset=0
offset *= cachep->colour_off;
/* Get slab management. */
freelist = alloc_slabmgmt(cachep, page, offset,
local_flags & ~GFP_CONSTRAINT_MASK, page_node);
//将slab信息赋值给page结构中,下面 有函数。cache和freelists字段
slab_map_pages(cachep, page, freelist);
//给page->freelists空闲链表赋值。freelists[0]=0,freelists[1]=1.....
cache_init_objs(cachep, page);
return page;
opps1:
kmem_freepages(cachep, page);
failed:
if (gfpflags_allow_blocking(local_flags))
local_irq_disable();
return NULL;
}
static void *alloc_slabmgmt(struct kmem_cache *cachep,
struct page *page, int colour_off,
gfp_t local_flags, int nodeid)
{
void *freelist;
void *addr = page_address(page);
page->s_mem = addr + colour_off;//这里给已申请的页帧的s_mem字段赋值
page->active = 0;//同时给页帧的active赋值
//我们把当前页的最后面放上空闲链表结构,空闲链表大小按照上面128的缓存来说大小是32字节
/* We will use last bytes at the slab for freelist */
freelist = addr + (PAGE_SIZE << cachep->gfporder) -
cachep->freelist_size;
return freelist;
}
static void slab_map_pages(struct kmem_cache *cache, struct page *page,
void *freelist)
{
page->slab_cache = cache;
page->freelist = freelist;
}
static void cache_init_objs(struct kmem_cache *cachep,
struct page *page)
{
int i;
for (i = 0; i < cachep->num; i++) {
set_free_obj(page, i, i);
}
}
static inline void set_free_obj(struct page *page,
unsigned int idx, freelist_idx_t val)
{
((freelist_idx_t *)(page->freelist))[idx] = val;
}
static __always_inline int alloc_block(struct kmem_cache *cachep,
struct array_cache *ac, struct page *page, int batchcount)
{
/*
* There must be at least one object available for
* allocation.
*/
//循环给cpu缓存entry赋值
while (page->active < cachep->num && batchcount--) {
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
ac->entry[ac->avail++] = slab_get_obj(cachep, page);
}
return batchcount;
}
static void *slab_get_obj(struct kmem_cache *cachep, struct page *page)
{
void *objp;
objp = index_to_obj(cachep, page, get_free_obj(page, page->active));
page->active++;
//从这里可以看出page->active指的本页框中已经分配出去的slab
return objp;
}
static inline freelist_idx_t get_free_obj(struct page *page, unsigned int idx)
{
return ((freelist_idx_t *)page->freelist)[idx];
}
static inline void *index_to_obj(struct kmem_cache *cache, struct page *page,
unsigned int idx)
{
return page->s_mem + cache->size * idx;
}
static void cache_grow_end(struct kmem_cache *cachep, struct page *page)
{
struct kmem_cache_node *n;
void *list = NULL;
check_irq_off();
if (!page)
return;
INIT_LIST_HEAD(&page->lru);
//获取当前节点的kmem_cache_node 结构
n = get_node(cachep, page_to_nid(page));
spin_lock(&n->list_lock);
//slab的个数+1,这个slab是2^n个页,每个slab中包含num个object
n->total_slabs++;
//如果这个slab中没有一个object被分配出去,就添加到free链表后面,否则添加到部分空后面
if (!page->active) {
list_add_tail(&page->lru, &(n->slabs_free));
n->free_slabs++;
} else
fixup_slab_list(cachep, n, page, &list);
STATS_INC_GROWN(cachep);
//当前slab中还有多少没有分配出去的object。这个和page->active不一样,page的是
表明已经分配出去的或者已经放入到cpu cache中的object
n->free_objects += cachep->num - page->active;
spin_unlock(&n->list_lock);
fixup_objfreelist_debug(cachep, &list);
}
主要流程就是先从cpu的cache中分配,如果没有refill,从伙伴系统申请一个页,然后添加batch个object到cpu cache中,再继续从cpu cache中返回一个,齐活。
整个申请的函数,一共还附加的给这个slab的第一个页帧中的一些内容赋值(注意一个slab是有2^n个page组成,每个slab中包含num个object)。比如page->active,这里表明了整个slab中已经被分配出去的object或者已经放入到cpu cache中的object的个数;page->s_mem指向第一个object的地址;page->freelist指向slab的空闲链表。
shared字段:该字段是用在smp系统中,解决的是多cpu共享的问题。比如skb_buff,在cpu1上申请,经过处理后由cpu2发出。这样会导致cpu1的缓存array耗尽,并不断的refill进来.cpu2的缓存array不断地增加需要不断向伙伴系统释放,二者结合会增加性能消耗。所以对于这种情况多cpu申请的array放入到shared中,每个cpu释放这种类型的内存同样也释放到shared中,这样的情况下我们申请内存的方式变成为 1、每cpu缓存array;2、多cpu shared array ;3、伙伴系统refill。