Chinaunix首页 | 论坛 | 博客
  • 博客访问: 232116
  • 博文数量: 37
  • 博客积分: 933
  • 博客等级: 军士长
  • 技术积分: 511
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-16 10:15
文章分类
文章存档

2012年(1)

2011年(36)

分类: LINUX

2011-03-19 21:20:27

 
                                                            author:zhangliang
                                                            mail  :
 
slab分配器引入的动机:最关键的一点性能的提升。
在没有slab之前,系统物理内存的分配都是以页为单位的,这将引发一下两点的问题:
1)系统只能以整页为单位进行物理内存的分配和回收,这对系统资源是一种极大的浪费。
2)对于硬件cache的污染问题特别严重,导致性能很大的损失。通俗的讲由于系统的硬件cache
大小有限,极端情况下系统将会一直忙于硬件cache的刷新,我们都知道访问内存的速度和访问硬件
高速cache的速度差别。
3)系统在运行期间,内核函数倾向于反复请求同一类型的内存区,例如只要内核创建一个新进程,它就要为
一些固定大小的表分配内存区。
而slab分配器的引入很好的解决了上述问题。
首先,slab使得分配小内存成为可能,例如分配32,64,128字节的内存等等,这也是slab分配器最直接的目的。
其次,小内存的得以分配的同时,L1 DATA CACHE的命中率也得到极大的提升。
(我们设想CPU 只有1级缓存,事实上2.6的内核也只对1级缓存进行优化)
为了更好的理解slab是如何运作的,我们先看一些必要的数据结构,顺后我们会随着代码进行深入的理解。
  1. struct kmem_cache:

  2.  373 struct kmem_cache {
  3.  374 /* 1) per-cpu data, touched during every alloc/free */
  4.  375 struct array_cache *array[NR_CPUS];
  5.  376 unsigned int batchcount;
  6.  377 unsigned int limit;
  7.  378 unsigned int shared;
  8.  379 unsigned int buffer_size;
  9.  380 /* 2) touched by every alloc & free from the backend */
  10.  381 struct kmem_list3 *nodelists[MAX_NUMNODES];
  11.  382 unsigned int flags; /* constant flags */
  12.  383 unsigned int num; /* # of objs per slab */
  13.  384 spinlock_t spinlock;
  14.  385
  15.  386 /* 3) cache_grow/shrink */
  16.  387 /* order of pgs per slab (2^n) */
  17.  388 unsigned int gfporder;
  18.  389
  19.  390 /* force GFP flags, e.g. GFP_DMA */
  20.  391 gfp_t gfpflags;
  21.  392 /*用于slab着色,方便CPU CACHE LINE对齐*/
  22.  393 size_t colour; /* cache colouring range */
  23.  394 unsigned int colour_off; /* colour offset */
  24.  395 struct kmem_cache *slabp_cache;
  25.  396 unsigned int slab_size;
  26.  397 unsigned int dflags; /* dynamic flags */
  27.  398
  28.  399 /* constructor func */
  29.  400 void (*ctor) (void *, struct kmem_cache *, unsigned long);
  30.  401
  31.  402 /* de-constructor func */
  32.  403 void (*dtor) (void *, struct kmem_cache *, unsigned long);
  33.  404
  34.  405 /* 4) cache creation/removal */
  35.  406 const char *name;
  36.  407 struct list_head next;
  37.  408
  38.  435 };

我们删除了暂时不必要的字段,这样看起来更加清晰明了,同时代码中也给了我们良好的注释信息。
这个结构体是用来描述高速缓存。需要注意的是:这个缓存与CPU CACHE是没有关系的,只是内存的一个或者几个连续页框。具体占用几个由unsigned int gfporder字段决定。
1)结构体的第一个元素为struct array_cache *array[NR_CPUS];这个字段是用来描述per-cpu高速缓存的,设计思想与页框分配器的
"每cpu页高速缓存"思想一致,如果你对后者不是很了解,OK,没关系,可以暂时忽略。每个CPU都有一个这样的对应结构体,也许你会
问为什么要每个CPU一个,所有CPU对应一个不可以吗?答案是这样做的好处是为了减少对锁得竞争,设想所有CPU共用一个这样的结构体,那么
你在想获取其中描述的对象之前必选要获得锁,才可以进行操作。

263 struct array_cache {
 264         unsigned int avail;
 265         unsigned int limit;
 266         unsigned int batchcount;
 267         unsigned int touched;
 268         spinlock_t lock;
 269         void *entry[0];         /*
 270                                  * Must have this definition in here for the proper
 271                                  * alignment of array_cache. Also simplifies accessing
 272                                  * the entries.
 273                                  * [0] is for gcc 2.95. It should really be [].
 274                                  */
 275 };
以上为struct array_cache的内容,在这里对各个参数进行解释是毫无必要的,但是有一点还是需要我们提前进行关注一下
最后的大小为0的指针数组,通过注释我们也可以看到,这是gcc(Version >=2.95)的特性,设想这样一个场景:

那么这个结构体得0字节数组指针就会指向物理内存上与struct array_cache A紧挨着的数据。同时另外的一个优点就是这个指针
不占用任何物理空间。这个特点很有用处,我们可以随着文章的深入了解到。(entry指针)
2) struct kmem_list3
这个结构体得定义如下:

 

  1. 289 struct kmem_list3 {
  2.  290 struct list_head slabs_partial; /* partial list first, better asm code */
  3.  291 struct list_head slabs_full;
  4.  292 struct list_head slabs_free;
  5.  293 unsigned long free_objects;
  6.  294 unsigned long next_reap;
  7.  295 int free_touched;
  8.  296 unsigned int free_limit;
  9.  297 unsigned int colour_next; /* Per-node cache coloring */
  10.  298 spinlock_t list_lock;
  11.  299 struct array_cache *shared; /* shared per node */
  12.  300 struct array_cache **alien; /* on other nodes */
  13.  301 };

 

其中包含三个链表 全满的slab,部分满的slab,和空闲的slab。

 

关系一眼明了。
3)结构体中还有一对构造函数和析构函数,不过没有实现。面向对象的思想,不错。调试的时候,你可以用一下。定义为自己的函数。
但是必须成对的定义,不能只定义一个,而不顾另一个。

 

随后我们还是跟着代码一起进军吧,看看slab分配器最初是如何建立起来的。

系统在初始化时 调用kmem_cache_init();这个函数在mem_init()之后调用,而我们知道mem_init负责伙伴系统的初始化。
也就是说:这时的伙伴系统已经建立,我们已经可以利用其进行连续页框的分配。
start_kernel()-->kmem_cache_init()-->kmem_list3_init()
   
在这个函数中首先初始化kmem_list3链表:
struct kmem_list3 __initdata initkmem_list3[NUM_INIT_LISTS];__initdata表示这个全局变量在装载时放入初始化数据段。
以便系统初始化好后,进行内存空间的释放。
调用kmem_list3_init函数对kmem_list3结构体进行初始化。把其中的三个链表(slabs_partial,slabs_full,slabs_free)初始化为指向
自己。同时把cache_cache的nodelist字段初始化为空。
cache_cache为kmem_cache_t类型,我们需要额外的说明一下。
在系统初始化期间,我们还没有建立起slab分配器,还无法进行小内存区域的分配,所以内核在初始化时手动的建立一些数据结构来构筑slab分配器

cache_cache为系统的第一个高速缓存,后续的数据结构的建立全部依赖与此高速缓存。cache_cache中很多成员都在系统编译时期得到初始化。
从其名字我们也可以得知一二,高速缓存的缓存。
我们在kmem_cache_init()中,对cache_cache剩余的变量进行初始化
 cache_cache.nodelists[0] = NULL //指向kmem_list指针为空
1223         INIT_LIST_HEAD(&cache_chain);
1224         list_add(&cache_cache.next, &cache_chain);//将cache_cache 加入cache_chain中cache_cache.colour_off = cache_line_size();//cache_line_size()函数获得你的计算机的当前cache line大小,方便对齐,提高性能
        //现在一般为128bytes,可以查看自己计算机的配置

(见二)

阅读(1956) | 评论(0) | 转发(0) |
0

上一篇:__alloc_pages 浅淡

下一篇:Slab 分析(二)

给主人留下些什么吧!~~