内核缓冲区的管理
关于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