这还是暑假之前写的总结... 这几天一个kernel群里老有人问关于slab方面的问题... 所以就在这里把些的总结贴一下... 献丑了... Slab Allocator 逻辑结构如下图所示: ![]() 如图,主要结构包括 cache 以及 slabs。故相应的数据结构有 cache 描述符和 slab 描述符。 cache 描述符 struct kmem_cache_s { /* full, partial first, then free */ struct list_head slabs_full; struct list_head slabs_partial; struct list_head slabs_free; unsigned int objsize; unsigned int flags; /* constant flags */ unsigned int num; /* # of objs per slab */ spinlock_t spinlock; #ifdef CONFIG_SMP unsigned int batchcount; #endif unsigned int gfporder; unsigned int gfpflags; size_t colour; /* cache colouring range */ unsigned int colour_off; /* colour offset */ unsigned int colour_next; /* cache colouring */ kmem_cache_t *slabp_cache; unsigned int growing; unsigned int dflags; /* dynamic flags */ /* constructor func */ void (*ctor)(void *, kmem_cache_t *, unsigned long); /* de-constructor func */ void (*dtor)(void *, kmem_cache_t *, unsigned long); unsigned long failures; char name[CACHE_NAMELEN]; struct list_head next; #ifdef CONFIG_SMP cpucache_t *cpudata[NR_CPUS]; #endif #if STATS unsigned long num_active; unsigned long num_allocations; unsigned long high_mark; unsigned long grown; unsigned long reaped; unsigned long errors; #ifdef CONFIG_SMP atomic_t allochit; atomic_t allocmiss; atomic_t freehit; atomic_t freemiss; #endif #endif }; 各个字段含义如下: slab_* 保存slabs的链表,如上图,full、partial和empty。 objsize 装入缓存器中的每个”对象”的大小。 flags 决定当处理这个缓存器时,分配器的部分行为。见8.1.2 num 每个slab包含的”对象”的数量 spinlock 保护这个结构的自旋锁,防止并发访问 batchcount 决定对于每个CPU的缓存器,一次所分配的”对象”的数量 gfporder 表示slab的页面的数量。每slab有2^gfporder个页面。因为这 是伙伴分配器所提供的分配大小。 gfpflags 保存当调用伙伴分配器分配页面时所用到GFP标识。 colour 每个slab会尽可能的将”对象”保存到不同的高速缓存行。缓存器着色 会在8.1.5节深入讨论。 colour_off 保持slabs对齐到的字节数。例如,对于size-X缓存器里的slab会对齐到 L1高速缓存。 colour_next 下一个要使用的颜色行。当这个值达到colour时就会反绕到0。 growing 设置这个标识可以表示这个缓存器的大小是否增加了。如果增加了, 则在内存紧张时,选择它来获取空闲的slabs的可能性会更小。 dflags 在缓存器存在期间会改变的动态标识。 ctor 一个复杂的”对象”可以可选的提供一个构造函数来初始化每个新 的”对象”。这是指向那个函数的指针,而且可以是NULL。 dtor 析构函数的指针,可以是NULL。 failure 在代码中不在使用这个字段,因此都初始化为0。 name 每个缓存器的名称 next 缓存器链表上的下一个缓存器。 cpudata 每CPU数据,指向描述一小组可用”对象”的管理结构。 num_active 在缓存器中当前活跃的”对象”的数量 num_allocations 在缓存器中已经分配了的”对象”的数量的总数。 high_mark num_active能够达到的最大的值 grown kmem_cache_grow()被调用的次数 reaped 缓存器被释放的次数 allochit 分配时,从每CPU缓存中分配”对象”的次数 allocmiss 与allochit相反 freehit 将”对象”释放到每CPU缓存中的次数 freemiss 与freehit相反。 这个结构相当的大,Slab Allocator中对每种”对象”都有一个对应的cache。每个cache管理 多个slab。Slab才是真正存放”对象”的地方。管理这些”对象”的结构就是slab描述符。 Slab 描述符 typedef struct slab_s { struct list_head list; unsigned long colouroff; void *s_mem; /* including colour offset */ unsigned int inuse; /* num of objs active in slab */ kmem_bufctl_t free; } slab_t; 各个字段含义如下: list 此 slab 所属于的链表。这个字段是在缓存器中的 slab_full 或者 slab_patial 或者 slab_free 上。 colouroff 这是离在 slab 中的第一个”对象”的基址的着色偏移值。 第一个对象的 地址是 s_mem + colouroff。 s_mem 给出在 slab 中第一个”对象”的起始地址。 inuse 在 slab 中的活跃”对象”的个数。 free 存储空闲”对象”的 bufctls 数组。参见 8.2.3 中的详细介绍。 这个结构仅仅是用来管理”对象”的,在创建 slab 时需要分配存放”对象”的内存,所需内存 从 buddy allocator 中申请,内存大小为其所属 cache 中 gfporder 指定。显然,所申请的内 存是连续的 2^cachep->gfporder 个页面。Slab 描述符可以存放在这个所分配的内存起始 处,也可以放在另外的地方。如图: ![]() ![]() 通用缓存器组 typedef struct cache_sizes { size_t cs_size; kmem_cache_t *cs_cachep; kmem_cache_t *cs_dmacachep; } cache_sizes_t; 各字段含义如下: cs_size 这个通用缓存器的大小 cs_cachep 用于在普通内存中分配”对象”的缓存器指针 cs_dmacachep 用于在 DMA 内存中分配”对象”的缓存器指针 在系统中静态定义了这个类型的一个数组,其主要为大小不定的一些通用结构分配内存。 例如,如过 Slab 描述符的位置是 Off_Slab,则这个 Slab 描述符以及其后的 kmem_bufctl_t[] 就放在这个通用缓存器组中的某一个缓存器里。 下面所要将到的数据结构是针对 SMP 系统进行优化所用到的数据结构: 每 CPU cache typedef struct cpucache_s { unsigned int avail; unsigned int limit; } cpucache_t; avail 在这个 cpucache 上空闲”对象”的数量。 limit 可以存在的空闲”对象”的总数。 每个 CPU 都有一个对应的小的本地 cache,这个 cache 并不是上面所提到的 cache 描述符。 这个 cache 仅仅是特定于某个 cache 描述符所管理的”对象”的数组。如 mm_struct 相关 的 cache 描述符所管理的”对象”就是 mm_struct 类型的,则这个 CPU 的本地 cache 就是 mm_struct 类型的数组。在为某 CPU 分配 cpucache_t 结构时,除了分配 cpucache_t 结构 所需要的内存外,也分配 limit*sizeof(void *)字节的内存。当然,它们是连续的。 如代码所示: ccnew = kmalloc(sizeof(void*)*limit+ sizeof(cpucache_t), GFP_KERNEL); ccnew 就是 cpucache_t *类型。 因此 cpucache_t 结构后面就是 limit 个 void 类型的指针。 所以 avail 还有一个作用就是作 为空闲”对象”的索引。如在从这个 CPU 本地 cache 分配一个”对象”时就有如下代码: obj = cc_entry(cc)[--cc->avail]; 其中: #define cc_entry(cpucache) \ ((void **)(((cpucache_t*)(cpucache))+1)) 这个代码就可以解释上面所说的一切。 更新每 CPU cache 信息所用到的数据结构 typedef struct ccupdate_struct_s { kmem_cache_t *cachep; cpucache_t *new[NR_CPUS]; } ccupdate_struct_t; cachep 是正在被更新的缓存器的指针,new 是系统中每个 CPU 的 cpucache 描述符的数组。 当然仅有在 SMP 系统上设置 SMP 选项时才可用。 在每 CPU cache 信息改变之后,在更新每个 CPU cache 信息时就不需要考虑并发性带来 的一致性问题,因为每个 CPU 只为从对应的 ccpudata_struct_t->new[ID]中获取新信息来 更新自己本地的 cache。这样就不需要用自旋锁来保护。 |
|
来自: ShangShujie > 《linux》