ps: 笔记以linux4.9版本源代码为基础,为了省略一些代码,使用 ‘–>’ 来表示包含的函数。
先查看下proc下面的slabinfo
从上面这个图,可以看到slab有很多的包含很多的对象,根据ULK里提到,高速缓存可以分规为两类:
- 普通高速缓存
- 专用高速缓存
有什么区别呢?专用高速缓存是由kmem_cache_creat()函数创建的,专门使用与特殊类型的对象,例如:dentry buffer_head task_struct 等等,我们熟悉的结构体。普通高速缓存就是一些不是专用的,使用kmalloc进行分配的,例如:kmalloc-8 kmalloc-16 dma-kmalloc-8之类的对象。
从kmalloc 看slab分配过程:
static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
unsigned long caller)
{
struct kmem_cache *cachep;
void *ret;
//kmalloc_slab 根据需要分配的大小来选择哪一个kmem_cache
cachep = kmalloc_slab(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(cachep)))
return cachep;
//下面分析
ret = slab_alloc(cachep, flags, caller);
....
}
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
....
//获取cpu本地高速缓存
ac = cpu_cache_get(cachep);
if (likely(ac->avail)) { //有空闲obj
ac->touched = 1;
objp = ac->entry[--ac->avail];
STATS_INC_ALLOCHIT(cachep);
goto out;
}
STATS_INC_ALLOCMISS(cachep);
//cpu本地高速缓存没有空闲obj,就进入下一步分配
objp = cache_alloc_refill(cachep, flags);
ac = cpu_cache_get(cachep);
out:
if (objp)
kmemleak_erase(&ac->entry[ac->avail]);
return objp;
}
先说明下struct array_cache 结构体:
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指向对象的起始地址
};
cachep->cpu_cache就是kmem_cache里包含的struct array_cache __percpu *cpu_cache。
ac = cpu_cache_get(cachep);
--> return this_cpu_ptr(cachep->cpu_cache);
分析下要使用cpu高速缓存进行读取操作:
因为每个CPU都有它们自己的硬件高速缓存(L1,L2,L3缓存),当此CPU上释放对象时,可能这个对象很可能还在这个CPU的硬件高速缓存中。
所以内核为每个CPU维护cpu_cache 一个这样的链表,当需要新的对象时,会优先尝试从当前CPU的本地CPU空闲对象链表(cpu_cache)获取相应大小的对象。如果命中的话(数据不会一直存在硬件缓存内的,会被其他数据换出),硬件缓存就不会有进行换进换出的动作了,大大减少了数据操作时间。这个本地CPU空闲对象链表在系统初始化完成后是一个空的链表,只有释放对象时才会将对象加入这个链表。
即操作会优先在cpu本地的高速缓存上进行读取,有空闲位置的话就直接进行使用,没有的话就进行第2步操作。那什么时候会进行第2步操作呢?
即 ac->avail == 0 , avail是表示当前cpu上有多少个可用的对象 , ==0即没有可以使用的对象了
如有有空闲对象的话就直接分配
objp = ac->entry[--ac->avail];
由于ac是记录着这次struct arrary_cache结构体存放地址,通过ac->entry数组,我们就得到下一紧接地址,这个地址可以看做是为本高速缓存内存的内存对象指针存放首地址,这里可以看出,我们是从最后一个对象开始分配的即avail对应的空闲对象是最热的,即最近释放出来的,更有可能驻留在CPU高速缓存中。
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();// 获取node节点
(ps : 不知道node节点的建议先看下
http://blog.csdn.net/gatieme/article/details/52384058 )
ac = cpu_cache_get(cachep);// 获取cpu本地高速缓存
batchcount = ac->batchcount; //获取cpu本地高速缓存一次性需要读入的对象数
if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {// BATCHREFILL_LIMIT 16
//如果cpu本地高速缓存近期没有被访问过,且batchcount大于16
//那么为啥要有这个条件,下面这段有解释
/*
* 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); //获取kmem_cache_node
// 那么为啥要根据node来进行kmem_cache_node获取呢
//先看struct kmem_cache 结构里的定义
//struct kmem_cache_node *node[MAX_NUMNODES];
// MAX_NUMNODES为node的个数,即每个node节点维护一个kmem_cache_node
//node节点有自己都对应一个本地物理内存,所以要区分具体看上面这个链接
BUG_ON(ac->avail > 0 || !n);
shared = READ_ONCE(n->shared);
if (!n->free_objects && (!shared || !shared->avail))
//空闲链表里没有空闲的obj 而且(没有cpu本地高速缓存或者没有可用的obj)
goto direct_grow;
spin_lock(&n->list_lock);
shared = READ_ONCE(n->shared);
/* See if we can refill from the shared array */
/*如果有则进行obj传输,下面具体分析*/
if (shared && transfer_objects(ac, shared, batchcount)) {
shared->touched = 1;
goto alloc_done;
}
while (batchcount > 0) {
/* Get slab alloc is to come from. */
page = get_first_slab(n, false); //下面分析
if (!page) //如果还是没有分配到页就跳到 must_grow
goto must_grow;
check_spinlock_acquired(cachep);
batchcount = alloc_block(cachep, ac, page, batchcount);
fixup_slab_list(cachep, n, page, &list);
}
//下面是重新分配一个新的slab
must_grow:
n->free_objects -= ac->avail;
alloc_done:
spin_unlock(&n->list_lock);
fixup_objfreelist_debug(cachep, &list);
direct_grow:
if (unlikely(!ac->avail)) {
/* Check if we can use obj in pfmemalloc slab */
if (sk_memalloc_socks()) {
void *obj = cache_alloc_pfmemalloc(cachep, n, flags);
if (obj)
return obj;
}
//这里是主要分析的地方,下面进行进一步分析
page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
/*
* cache_grow_begin() can reenable interrupts,
* then ac could change.
*/
ac = cpu_cache_get(cachep);
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 int transfer_objects(struct array_cache *to,
struct array_cache *from, unsigned int max)
{
/* Figure out how many entries to transfer */
int nr = min3(from->avail, max, to->limit - to->avail);
//从 from->avail :cpu共享高速缓存
// max: batchcount 最大读取数量
// to->limit - to->avail : cpu本地高速缓存可以接受的大小
if (!nr)
return 0;
memcpy(to->entry + to->avail, from->entry + from->avail -nr,
sizeof(void *) *nr);
//将共享高速缓存的多个obj拷贝到本地高速缓存内
from->avail -= nr;
to->avail += nr;
return nr;
}
static struct page *get_first_slab(struct kmem_cache_node *n, bool pfmemalloc)
{
struct page *page;
page = list_first_entry_or_null(&n->slabs_partial,
struct page, lru); //从slabs_partial里读出一页
if (!page) {
n->free_touched = 1;
page = list_first_entry_or_null(&n->slabs_free,
struct page, lru); ///从slabs_free里读出一页
}
// 这里 pfmemalloc == false 所以没啥用,这条不分析了
if (sk_memalloc_socks())
return get_valid_first_slab(n, page, pfmemalloc);
return page;
}
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.
*/
BUG_ON(page->active >= cachep->num);
while (page->active < cachep->num && batchcount--) {
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
//将slab里的对象放到cpu本地高速缓存里
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++;
#if DEBUG
if (cachep->flags & SLAB_STORE_USER)
set_store_user_dirty(cachep);
#endif
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)
{
//page->s_mem指向首个obj的地址,cache->size为obj大小,idx为已经使用的obj的个数。
return page->s_mem + cache->size * idx;
}
到这里我们已经知道怎么取空闲的obj了,不过整个slab什么结构,我们还不知道,先分析下slab的结构组成,如图所示:
4.9内核较之前面的版本有些许区别,他把freelist放在了对象的后面。
然后我们就得分析这玩意是怎么来的。
分析两段代码:
1.cache_alloc_refill 的direct_grow标签后面部分
2.kmem_cache_create创建kmem_cache
下面部分就不具体的分析了,代码多了看着蛋疼,这里只说怎么做出上面这图。
先说第一段代码direct_grow 下面的部分:
page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
{
....
//分配物理页
page = kmem_getpages(cachep, local_flags, nodeid);
....
//下面分析
freelist = alloc_slabmgmt(cachep, page, offset,
local_flags & ~GFP_CONSTRAINT_MASK, page_node);
....
//下面分析
cache_init_objs(cachep, page);
}
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; //这里即把page->s_mem指向页开始colour_off偏移处
page->active = 0; //以分配的obj数量,这里初始化为0
if (OBJFREELIST_SLAB(cachep)) //这里应该是根据cachep的flag是否设立freelist
freelist = NULL;
else if (OFF_SLAB(cachep)) { //这里是freelist与obj分离,需重新分配freelist
/* Slab management obj is off-slab. */
freelist = kmem_cache_alloc_node(cachep->freelist_cache,
local_flags, nodeid);
if (!freelist)
return NULL;
} else {//这里是freelist与obj在一起,地址相连
/* We will use last bytes at the slab for freelist */
freelist = addr + (PAGE_SIZE << cachep->gfporder) -
cachep->freelist_size;
//这里我们不看分离情况
//我们看到freelist指向了slab最后向前freelist_size的地址处
//那么freelist_size大小是多少呢?freelist指向的地址在哪呢?
//不知道的话,我们下面会分析
}
return freelist;
}
cache_init_objs(cachep, page);
-->shuffled = shuffle_freelist(cachep, page);
-->{
count = cachep->num;
for (i = 0; i < count; i++)
set_free_obj(page, i, i);
}
--> set_free_obj(page, i, i);
static inline void set_free_obj(struct page *page,
unsigned int idx, freelist_idx_t val)
{
//这里从0开始排,排到count-1大小
//那cachep->num是多少呢?下面分析
((freelist_idx_t *)(page->freelist))[idx] = val;
}
//这此为止我们已经知道slab的具体结构了,但是排列间隔及大小我们还不清楚,然后我们再进行下一步分析。
第二步分析kmem_cache_creat:
struct kmem_cache * kmem_cache_create(const char *name, size_t size, size_t align,
unsigned long flags, void (*ctor)(void *))
--> create_cache
--> __kmem_cache_create //这里有几个重要的东西,先分析下
int __kmem_cache_create (struct kmem_cache *cachep, unsigned long flags)
{
size_t size = cachep->size;
....
//关于size的代码有点分散,简要说明下,size是指的是obj的大小加上一个偏移,作 用是为了进行对齐操作
....
f (ralign < cachep->align) {
ralign = cachep->align;
}
....
cachep->align = ralign; // cachep->align是kmem_cache_create指定大小,ralign这里是字 大小
cachep->colour_off = cache_line_size(); //读取缓存行,缓存行一般64字节(注:这里说到着色,下面可以具体分析一下)
/* Offset must be a multiple of the alignment. */
if (cachep->colour_off < cachep->align)
cachep->colour_off = cachep->align;
....
if (set_objfreelist_slab_cache(cachep, size, flags)) { //下面分析
flags |= CFLGS_OBJFREELIST_SLAB;
goto done;
}
....
done:
cachep->freelist_size = cachep->num * sizeof(freelist_idx_t); // freelist_size大小就是freelist 数量* freelist大小
cachep->flags = flags;
cachep->allocflags = __GFP_COMP;
if (flags & SLAB_CACHE_DMA)
cachep->allocflags |= GFP_DMA;
cachep->size = size;// size是obj大小+偏移
cachep->reciprocal_buffer_size = reciprocal_value(size);
....
}
static bool set_objfreelist_slab_cache(struct kmem_cache *cachep,
size_t size, unsigned long flags)
{
size_t left;
cachep->num = 0;
if (cachep->ctor || flags & SLAB_DESTROY_BY_RCU)
return false;
left = calculate_slab_order(cachep, size,
flags | CFLGS_OBJFREELIST_SLAB); //下面分析
if (!cachep->num)
return false;
if (cachep->num * sizeof(freelist_idx_t) > cachep->object_size) // freelist_size不能大于 //object_size
return false;
cachep->colour = left / cachep->colour_off; // slab 的 颜色
return true;
}
calculate_slab_order
--> num = cache_estimate(gfporder, size, flags, &remainder);
static unsigned int cache_estimate(unsigned long gfporder, size_t buffer_size,
unsigned long flags, size_t *left_over)
{
unsigned int num;
size_t slab_size = PAGE_SIZE << gfporder;
if (flags & (CFLGS_OBJFREELIST_SLAB | CFLGS_OFF_SLAB)) {
num = slab_size / buffer_size;
*left_over = slab_size % buffer_size;
} else { // 我们看下面
num = slab_size / (buffer_size + sizeof(freelist_idx_t)); //可以看出num 大小就等于
//slab的大小除以(buffer_size + sizeof(freelist_idx_t) 注:buffer_size就等于 //cachep->size
*left_over = slab_size %
(buffer_size + sizeof(freelist_idx_t)); //剩下的空间大小,与slab颜色相关
}
return num;
}
好了,以上就是分析了slab的具体结构来源,现在分析一下遗留的问题,slab的着色。
slab的着色:
先找源头,即上图slab结构的colour_off,哪里来的呢?回到分配新的slab那里,
cache_grow_begin函数中
n->colour_next++;//上一个slab的颜色偏移 ++
if (n->colour_next >= cachep->colour) // 在上面提到过得 cachep->colour
n->colour_next = 0;
offset = n->colour_next;
if (offset >= cachep->colour)
offset = 0;
offset *= cachep->colour_off; //这里是就offset == colour_off
提一下 cachep->colour 怎么来的:
上面cache_estimate函数中剩下的空间大小left_over,往回走到调用它的函数
set_objfreelist_slab_cache里面,这里写到:
cachep->colour = left / cachep->colour_off;
left就是上网的left_over
好了,现在都知道cachep->colour的来源了。着色即偏移。
然后是分析一下怎么进行slab里面obj的取和释放:
取:
即slab_get_obj 函数,上面已经挂上代码了,
释放:
怎么找释放地方呢?我们分析的是kmalloc,对应的是什么,当然是kfree。好了找到slab_put_obj 释放函数
static void slab_put_obj(struct kmem_cache *cachep,
struct page *page, void *objp)
{
unsigned int objnr = obj_to_index(cachep, page, objp);//获取释放的freelist组号
#if DEBUG //DEBUG 不用管它
unsigned int i;
/* Verify double free bug */
for (i = page->active; i < cachep->num; i++) {
if (get_free_obj(page, i) == objnr) {
pr_err("slab: double free detected in cache '%s', objp %p\n",
cachep->name, objp);
BUG();
}
}
#endif
page->active--;
if (!page->freelist)
page->freelist = objp + obj_offset(cachep);
set_free_obj(page, page->active, objnr); //在freelist组号为active里写下objnr号(即为刚释放的obj组号)
}
介绍这么多,具体是怎么样的还是配合图比较好容易理解
//建议看下面这个博客,写的真是好,我是也参考它进行分析的
http://www.cnblogs.com/tolimit/p/4566189.html?utm_source=tuicool&utm_medium=referral
好了回归正题,总结下slab的具体分配过程:
先到kmem_cache里面找cpu本地高速缓存里面找没有空闲的obj,没有再到cpu的共享高速缓存,再没有就重新建立一个空的slab。
最后将分配的obj移动到cpu本地高速缓存后再进行分配操作,即移动到ac->entry。
从cpu的共享高速缓存 到 cpu本地高速缓存:
transfer_objects(ac, shared, batchcount)
从slab 到 cpu本地高速缓存:
alloc_block(cachep, ac, page, batchcount);
有兴趣的可以去看一下。
观察仔细的朋友一定发现了,这样操作在一定形式上模仿了硬件的缓存机制。
cpu本地高速缓存 : L1高速缓存
cpu共享高速缓存 : L2高速缓存
slab : 内存
好了,先分析到这里,写的有些乱,大家见谅。