内核代码阅读(9) - 内核缓冲区的管理slab上

内核缓冲区的管理

关于slab

1) 内存分配的最小单位是page。
2) slab是为了加速内核中常用结构体的分配,对象池。
3) 每种slab有 constructor 和 destrutor。所以从slab种获取的对象可以直接使用。
4) 对象分为:
   大对象:结构体的大小比一个page页还要大。
   小对象:一个page页可以装下n个结构体。
5) 一个slab可能1,2,4,8。。。最多32个page构成。
6) 每个slab的前端是 slab_t 结构体,同一类型对象的slab通过 slab_t 链在一起。
7) 每个slab都有对象区,是对象的数组。
8) 每个slab上还有一个对象连接数组,用来把对象数组链接起来。
9) 每个slab头都有一个着色区,用来调整slab,使得和 cache line 对齐。

slab_t结构体

typedef struct slab_s {
        struct list_head        list;
        unsigned long                colouroff;
        void                        *s_mem;
        unsigned int                inuse;
        kmem_bufctl_t                free;
    } slab_t;
1) slab通过list链入3个队列中的一个:
   fully-used, partital, fully-free
2) 管理的是一个slab。

cache_cache

typedef struct kmem_cache_s kmem_cache_t;
    static kmem_cache_t cache_cache = {
        slabs:                LIST_HEAD_INIT(cache_cache.slabs),
        firstnotfull:        &cache_cache.slabs,
        objsize:        sizeof(kmem_cache_t),
        flags:                SLAB_NO_REAP,
        spinlock:        SPIN_LOCK_UNLOCKED,
        colour_off:        L1_CACHE_BYTES,
        name:                "kmem_cache",
    };
1) kmem_cache_t:
   同一种对象被slab_t组织成list,而链表的头部就是 kmem_cache_t
2) 所有的 kmem_cache_t 也是在slab中管理,而这种特殊的slab也是被slab_t链起来。
3) 特殊的slab的链表头就是 cache_cache。

slab_cache

并不是所有的数据结构都从cache_cache(专用的缓冲队列)中分配,不常用的构造开销也不大的从通用缓冲区队列申请,即为 slab_cache。
slab_cache 管理的是不同大小的对象32, 64, 最大到128K。

内核内存分配

1) 在专用缓冲区(cache_cache)中分配对象,需要指定一个队列 kmem_cache_t。
   void *kmem_cache_alloc(kmem_cache_t *cachep, int flags);
   void *kmem_cache_free(kmem_cache_t *cachep, void *objp);
2) 在通用缓冲区分配的函数,需要指定大小:
   void *kmalloc(size_t size, int flags);
   void kfree(const void *objp);
3) 如果要分配的数据结构大小接近一个页面,可以直接申请一个page
   struct page * alloc_pages(int gfp_mask, unsigned long order);
4) 内核虚存的分配
   void* vmalloc(unsigned long size);
   void vfree(void *addr);
   从内核的虚存空间(3G以上)分配一块虚存和相应的物理内存,这块内存kswapd不可见。所以不能被swap出去。

专用缓冲区 cache_cache 队列的建立

数据结构负责初始化自己的队列

void __init skb_init(void)
    {
        int i;
        skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                                              sizeof(struct sk_buff),
                                              0,
                                              SLAB_HWCACHE_ALIGN,
                                              skb_headerinit, NULL);
        if (!skbuff_head_cache)
                panic("cannot create skbuff cache");
        for (i=0; i<NR_CPUS; i++)
                skb_queue_head_init(&skb_head_pool[i].list);
    }
1) 为网络驱动创建 sk_buff 数据结构的专用缓冲区队列。
2) 名字是 skbuff_head_cache。
3) SLAB_HWCACHE_ALIGN
   要求对齐 cache_line
4) 构造函数是 skb_headerinit。
5) static kmem_cache_t *skbuff_head_cache;
   kmem_cache_create 的返回值是skbuff的slab的队列头,存放放在static变量里。
   使用 kmem_cache_alloc 分配skubuff的时候要传递这个队列头。

kmem_cache_create

1) 获取一个队列头
    cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
2) 初始化 kmem_cache_t 中的成员
   color:头部的着色区
   size:一个对象的大小
   num:一个slab中可以装下几个obj
   flags:决定 slab_t 是放在slab上还是独立存放。

专用缓冲区 kmem_cache_alloc 缓冲区的分配

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
    {
        struct sk_buff *skb;
        skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
        return skb;
    }
1) 分配的时候要把slab的队列头结构 skbuff_head_cache 传递给 kmem_cache_alloc。
static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags)
    {
        unsigned long save_flags;
        void* objp;
        kmem_cache_alloc_head(cachep, flags);
    try_again:
        local_irq_save(save_flags);
        objp = kmem_cache_alloc_one(cachep);
        local_irq_restore(save_flags);
        return objp;
    alloc_new_slab:
        local_irq_restore(save_flags);
        if (kmem_cache_grow(cachep, flags))
                goto try_again;
        return NULL;
    }
1) kmem_cache_alloc_head
   检查当前的cachep是否支持DMA
2) kmem_cache_alloc_one
   从cachep上分配一个object
3) 如果返回值为空,说明cachep的slab队列没有slab可供分配,则空过 kmem_cache_grow 增长cachep队列的大小。

kmem_cache_alloc_one

#define kmem_cache_alloc_one(cachep)                                \
    ({                                                                \
        slab_t        *slabp;                                        \
                                                                \
        /* Get slab alloc is to come from. */                        \
        {                                                        \
                struct list_head* p = cachep->firstnotfull;        \
                if (p == &cachep->slabs)                        \
                        goto alloc_new_slab;                        \
                slabp = list_entry(p,slab_t, list);        \
        }                                                        \
        kmem_cache_alloc_one_tail(cachep, slabp);                \
    })
1) 这是一段宏。
2) cachep->firstnotfull
   从cachep中找到第一个空闲的slab的list
3) slabp = list_entry(p,slab_t, list)
   通过list找到slabp的开头。
4) if (p == &cachep->slabs)
   如果cachep中没有空闲的slab了,则goto到 alloc_new_slab,进行分配。
5) 找到了一个slabp之后,kmem_cache_alloc_one_tail进行初始化。
static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep,
                                                         slab_t *slabp)
    {
        void *objp;
        STATS_INC_ALLOCED(cachep);
        STATS_INC_ACTIVE(cachep);
        STATS_SET_HIGH(cachep);
        slabp->inuse++;
        objp = slabp->s_mem + slabp->free*cachep->objsize;
        slabp->free=slab_bufctl(slabp)[slabp->free];
        if (slabp->free == BUFCTL_END)
                cachep->firstnotfull = slabp->list.next;
        return objp;
    }
1) 首先跟新cachep中的统计
2) slabp->inuse++;
   更新slabp中的obj的使用计数。
3) objp = slabp->s_mem + slabp->free*cachep->objsize;
   计算出obj的起始地址。
   s_mem: 是slab中obj的起始偏移量。
   free: 是链接数组的头部,第一个可以使用的obj的下标。
   cachep->objsize: 一个obj的大小
4) slabp->free=slab_bufctl(slabp)[slabp->free]
   free的下标指向下一个。
   slab_bufctl: 一个记录obj下标的数组。

kmem_cache_grow

static int kmem_cache_grow (kmem_cache_t * cachep, int flags)
    {
        slab_t        *slabp;
        struct page        *page;
        void                *objp;
        size_t                 offset;
        unsigned int         i, local_flags;
        unsigned long         ctor_flags;
        unsigned long         save_flags;
        offset = cachep->colour_next;
        cachep->colour_next++;
        if (cachep->colour_next >= cachep->colour)
                cachep->colour_next = 0;
        offset *= cachep->colour_off;
        cachep->dflags |= DFLGS_GROWN;
        cachep->growing++;
        
        objp = kmem_getpages(cachep, flags);
        slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags);
        i = 1 << cachep->gfporder;
        page = virt_to_page(objp);
        do {
                SET_PAGE_CACHE(page, cachep);
                SET_PAGE_SLAB(page, slabp);
                PageSetSlab(page);
                page++;
        } while (--i);
        kmem_cache_init_objs(cachep, slabp, ctor_flags);
        spin_lock_irqsave(&cachep->spinlock, save_flags);
        cachep->growing--;
        list_add_tail(&slabp->list,&cachep->slabs);
        if (cachep->firstnotfull == &cachep->slabs)
                cachep->firstnotfull = &slabp->list;
        STATS_INC_GROWN(cachep);
        cachep->failures = 0;
        return 1;
    }
1) offset = cachep->colour_next;
   计算着色区的便移量。目的是使各个slab中的obj在cache line中错开位子。
   TODO: 研究一下 cache line
2) objp = kmem_getpages(cachep, flags);
   申请物理页,返回物理页的首地址。
   addr = (void*) __get_free_pages(flags, cachep->gfporder);
3) slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags);
   初始化slabp中的slab_t结构。
4) i = 1 << cachep->gfporder;
   page = virt_to_page(objp);
   do {
           SET_PAGE_CACHE(page, cachep);
           SET_PAGE_SLAB(page, slabp);
           PageSetSlab(page);
           page++;
   } while (--i);
   这个while循环,把本次分配的page中的list的
       list.next = cachep;
       list.prev = slabp
   这个时候page已经从buddy的链表中脱离出来,list可以挪做它用了。
   记录一个page所属的cachep和slabp,为了在释放一个obj的时候,
   根据obj所在的物理页找到mem_map中的page结构体,然后根据list.prev, list.next找到cachep和slabp。
5) kmem_cache_init_objs(cachep, slabp, ctor_flags)
   初始化slab中的obj和ctl数组。
6) list_add_tail(&slabp->list,&cachep->slabs)
   把新的slabp挂在cachep后面,激活。
7) cachep->firstnotfull = &slabp->list;
   更新第一个空闲的slab
上一篇:内核代码阅读(11) - ioremap


下一篇:java之路,IO操作(字符集,序列化)