打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Slab分配器分析
一:筹备常识:

  前面我们分析过了大内存分配的实现机制,事实上,若为小块内存而要求全部页面,这样对内存来说是一种极度的挥霍。因而linux采取了slab来治理小块内存的分配与释放。Slab最早是由sun的工程师提出。它的提出是基于以下因素斟酌的:

  1:内核函数常常偏向于重复请求相同的数据类型。比方:创建过程时,会请求一块内存来存放mm结构。

  2:不同的结构使用不同的分配办法可以提高效率。同样,如果进程在取消的时候,内核不把mm结构释放掉,而是存放到一个缓冲区里,以后若有请求mm存储空间的行动就可以直接从缓冲区中取得,而不需从新分配内存.

  3:前面我们曾分析过,如果伙伴系统频繁分配,释放内存会影响系统的效率,以此,可以把要释放到的内存放到缓冲区中,直至超过一个阀值才把它释放至伙伴系统,这样可以在必定水平上缓减减伙伴系统的压力

  4:为了缓减“内碎片”的产生,通常可以把小内存块按照2的倍数组织在一起,这一点和搭档系统类似

  二:slab分配器概貌:

  Slab将缓存分为两种:一种是专用高速缓存,另外一种是普通高速缓存。请注意,这里所说的高速缓存和硬件没有必定的关联,它只是slab分配器中的一个软件概念。

  专用高速缓存顶用来存放内核应用的数据结构,例如:mm,skb,vm等等

  普通高速缓存是指存放普通的数据,好比内核为指针分配一段内存

  所有的高速缓存区都通过链表的方法组织在一起,它的首结点是cache_chain

  另外,普通高速缓存将分配辨别为32*(2^0),32*(2^1),32*(2^2) ….32*(2^12)大小,共13个区域大小,另外,每个大小均有两个高速缓存,一个为DMA高速缓存,一个是惯例高速缓存。它们都存放在一个名这cache_size的表中.

  Slab调配器把每一个恳求的内存称之为对象(跟oop设计方式中的对象相似,都有初始化与析构).对象又寄存在slab中.slab又依照空,末满,全满全体链接至高速缓存中.如下图所示:

  

  三:slab分配器相干的数据结构:

  高速缓存:

  typedef struct kmem_cache_s kmem_cache_t;

  struct kmem_cache_s {

  struct array_cache*array[NR_CPUS];/*per cpu构造,每次分配与开释的时候都先从这里取值与存值*/

  unsigned int batchcount; /*array[NR_CPUS]中没有空闲对象时,一次为数组所分配的对象数*/

  unsigned int limit; /* array[NR_CPUS]中所许可的最大空闲数 */

  struct kmem_list3 lists; /*将在后面分析*/

  unsigned int objsize; /*slab中的对象大小*/

  unsigned int flags; /* cache标记*/

  unsigned int num;/*每个slab中的对象数目 */

  unsigned int free_limit; /* upper limit of objects in the lists */

  spinlock_t spinlock;

  unsigned int gfporder; /*2^gfporder即为cache中slab的大小*/

  unsigned int gfpflags;

  size_t colour; /*着色机制,后面会详细分析 */

  unsigned int colour_off; /* colour offset */

  unsigned int colour_next; /* cache colouring */

  kmem_cache_t *slabp_cache;/*slab描述符放在缓存区外面时,此值指向描述符在普通缓存区中的地位*/

  unsigned int slab_size; /*每一个slab的大小*/

  unsigned int dflags; /* dynamic flags */

  void (*ctor)(void *, kmem_cache_t *, unsigned long); /*结构函数*/

  void (*dtor)(void *, kmem_cache_t *, unsigned long); /*析构函数*/

  const char *name; /*cache的名字*/

  struct list_head next; /*下一个cache 用来形成链表*/

  /* 5) statistics */

  #if STATS

  unsigned long num_active;

  unsigned long num_allocations;

  unsigned long high_mark;

  unsigned long grown;

  unsigned long reaped;

  unsigned long errors;

  unsigned long max_freeable;

  atomic_t allochit;

  atomic_t allocmiss;

  atomic_t freehit;

  atomic_t freemiss;

  #endif

  #if DEBUG

  int dbghead;

  int reallen;

  #endif

  }

  这里值得注意的处所是,与2.4比拟,slab部份的结构与成员位置发生了很大转变。个别来说常常用的成员放在结构体的前面。后面会解述为什么。 在cache这个结构里,有两个很主要的结构:struct array_cache *array[NR_CPUS]与struct kmem_list3 lists;详细分析一下

  struct array_cache {

  unsigned int avail; //当前空闲对象的位置

  unsigned int limit; //容许的闲暇对象的最大值

  unsigned int batchcount; //一次要填充给数组的对象数,或者一次要释放的对象数

  unsigned int touched; //如果从该组中分配了对象,则把此值置为1

  }

  struct kmem_list3 {

  struct list_head slabs_partial; /*末满的slab链 */

  struct list_head slabs_full; /*满的slab链*/

  struct list_head slabs_free; /*完整空闲的slab链*/

  unsigned longfree_objects; /*空链的对象数*/

  int free_touched;

  unsigned longnext_reap;

  struct array_cache*shared; /*全局shar数组。当array[NR_CPUS]满了之后,会将对象释放至此,array[NR_CPUS]请求对象时,会先在这里取得对象 */

  }

  Slab的数据结构

  struct slab {

  struct list_head list; /*用来构成链表*/

  unsigned long colouroff; /*着色机制,后面会详解*/

  void *s_mem; /* 首个对象的起始地址 */

  unsigned int inuse; /* slab中的使用对象个数 */

  kmem_bufctl_t free; /*slab中的第一个空闲对象的序号*/

  };

  四:slab中的着色机制

  在咱们分析具体的代码之前,有必要首先懂得一下slab的着色机制。

  Slab中援用着色机制是为了提高L1缓冲的效率。我们晓得linux是一个兼容性很高的平台,但现在处理器的缓冲区方式多种多样,有的每个处理器有自己的独破缓存。有的许多处理器共享一个缓存。有的除了一级缓存(L1)外仍是二级缓存(L2),因此,linux为了更好的兼容处理平台,只优化了一级缓存

  为了下面的分析,我们不妨假设一下:假设处理器每根缓存线为32字节,L1大小为16K (可以算出共有512根缓存线),每根缓存线与主存交互的大小也被称为cache line ,在这个例子中cache line是32字节

  只有当处理器检测到缓存线抵触时(读或者写生效),才会与主存交互,例如当处理器检测到缓存读失效,会将相应地址所在的32字节读取到缓存。有这里有一点要留神的是,缓存与主存的交互是按照块大小来的,即一次读或者写32字节。而且每条缓存线所读取的地址不是任意的。例如:第0根缓存总线只能读取 0~32 16K ~ 16K+32 32K~32K+32的地址

  Slab分配器请求对象按照cache line对齐,我们来看一下,如果没有对齐,会造成什么样的影响:

  假设对象为20个字节,一个对象的起始地址是0 位于第0条缓存线。第二个地址的0~9位于第0条缓存线,10~19位于第1条缓存线。可以设想一下,如果要读入第二个对象,就会刷新二个缓存,共64个字节的数据。若是按照cache line对齐,则只有刷新一次高速缓存,只要交互32字节的数据。

  当然,如果对象大小太小,我们是以cache line折半来对齐的,这我们在后面的代码中可以看到

  探讨完cache line对齐之后,我们再来看看什么叫着色。

  假设当初有这样的两个对象A,B A位于0~64于,第0条缓存线。B位于16K之后的64个字节,也是第0条缓存线。我们在上面的分析可以看到,常用的数据结构时常放在结构体的前面。此时就会有高32位的访问频率大大高于低32位的访问频率。假设此时,访问A之后再来访问B。高位访问50次,低位访问10次。

  我们来看看这时的情况:

  处理器在访问完A后,再访问B的地址,发现B需要缓冲线0,则把缓冲线0的数据写回主存,再读入B的值,拜访完B后,发明又要访问A,此时,又把B的值写回主存,再读入A的值 …

  按这样盘算,共须要和主存交互50*2 + 10*2 = 120次

  我们不妨把B后移32字节,这样B的高32位就位于缓存线1 低32位处于缓存线2。这时,访问完A后,访问B只要要把B的数据读科第1,第2缓存线,直接交互就行了。

  如果常常有这样的情形产生,就会发生极大的“平稳”,极度影响体系效力

  基于这样的情况。Slab 分器配把每一个slab都错开了,都往后移了一个或者是多个cache line

  详细情况可以参考:

   Board=linuxK&Number=83557&page=91&view=collapsed&sb =5&o=all&fpart=1&vc=1)

  在此十分感激linuxforum的lucian_yao,你的文章给了我极大的辅助 ^_^ 五:kmem_cache_create()分析

  我们以一个例子来跟踪分析一下slab的机制:

  下面是一个测试模块的代码:

  #include <linux/config.h>

  #include <linux/module.h>

  #include <linux/slab.h>

  MODULE_LICENSE("GPL");

  MODULE_AUTHOR("ericxiao <xgr178@163.com>");

  MODULE_DESCRIPTION("slab test module");

  static kmem_cache_t *test_cachep = NULL;

  struct slab_test

  {

  int val;

  };

  void fun_ctor(struct slab_test *object , kmem_cache_t *cachep , unsigned long flags )

  {

  printk("in ctor fuction ...\n");

  object->val = 1;

  }

  void fun_dtor(struct slab_test *object , kmem_cache_t *cachep , unsigned long flags)

  {

  printk("in dtor fuction ...\n");

  object -> val = 0;

  }

  static int __init init(void)

  {

  struct slab_test *object = NULL;

  printk("slab test moudle init ... \n");

  test_cachep = kmem_cache_create("test_cachep",sizeof(struct slab_test),0,SLAB_HWCACHE_ALIGN, \

  fun_ctor, fun_dtor);

  if(!test_cachep)

  return;

  object = kmem_cache_alloc( test_cachep, GFP_KERNEL );

  if(object)

  {

  printk("alloc one val = %d\n",object->val);

  kmem_cache_free( test_cachep, object );

  object = NULL;

  }else

  return;

  object = kmem_cache_alloc( test_cachep, GFP_KERNEL );

  if(object)

  {

  printk("alloc two val = %d\n",object->val);

  kmem_cache_free( test_cachep, object );

  object = NULL;

  }else

  return;

  }

  static void fini(void)

  {

  printk("test moudle exit ...\n");

  if(test_cachep)

  kmem_cache_destroy( test_cachep ); } module_init(init); module_exit(fini); 我们把模块加载之后,用dmesg的命令能够看到如下的输出信息:

  slab test moudle init ...

  in ctor fuction ...

  in ctor fuction ...

  ……

  alloc one val = 1

  alloc two val = 1

  将模块卸载之后可以看到:

  test moudle exit ...

  in dtor fuction ...

  ……

  从上我们可以看到,当从cache中分配一个对象时,会初始化良多object(dmesg输出信息中,呈现屡次in ctor fuction ...),当一个对象释放时,并没有立刻调用其析构函数。

  我们来看看详细的代码

  kmem_cache_create()是创立一个专用cache.同样的,所有专用缓冲区头部也由一个slab分配器保护,它的名字叫:cache_cache。其中每个大个对象的大小均为sizeof(cache).它是静态初始化的:

  static kmem_cache_t cache_cache = {

  .lists = LIST3_INIT(cache_cache.lists),

  .batchcount = 1,恋爱   叶问1,

  .limit = BOOT_CPUCACHE_ENTRIES,

  .objsize= sizeof(kmem_cache_t),

  .flags = SLAB_NO_REAP,

  .spinlock= SPIN_LOCK_UNLOCKED,

  .name = "kmem_cache",

  #if DEBUG

  .reallen= sizeof(kmem_cache_t),

  #endif

  };

  Kmem_cache_creat的代码在slab.c中,如下所示:

  //参数含意:

  //name:cache名字。Align:对齐量.flags:分配标志,ctor:初始化函数 ,心情日志,dtor析构函数

  kmem_cache_t *kmem_cache_create (const char *name, size_t size, size_t align,

  unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),

  void (*dtor)(void*, kmem_cache_t *, unsigned long))

  {

  size_t left_over, slab_size;

  kmem_cache_t *cachep = NULL;

  //参数检测名字不能为空,有析构函数,必需要用初始化函数,不能在中止中,对像不能太大也不能太小(不//能超过2^5个页)

  if ((!name) ||in_interrupt() ||(size < BYTES_PER_WORD) ||

  (size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) ||

  (dtor && !ctor)) {

  printk(KERN_ERR "%s: Early error in slab %s\n",

  __FUNCTION__, name);

  BUG();

  }

  if (flags & SLAB_DESTROY_BY_RCU)

  BUG_ON(dtor);

  //flag参数的有效性检讨

  if (flags & ~CREATE_MASK)

  BUG();

  //align参数的调剂。如无特别要求,align设为零,flag设为SLAB_HWCACHE_ALIGN。按照处理器缓//存对齐

  if (align) {

  flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);

  } else {

  if (flags & SLAB_HWCACHE_ALIGN) {

  //cache_line_size获得处置平始的cache line.前面已经剖析过

  align = cache_line_size();

  //如果对象太小,为了进步应用了,取cache line半数对齐

  while (size <= align/2)

  align /= 2;

  } else { align = BYTES_PER_WORD; } } //从cache_cache中分得一个缓存描述符 kmem_cache_alloc函数在后面讲述

  cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);

  if (!cachep)

  goto opps;

  //初始化

  memset(cachep, 0, sizeof(kmem_cache_t));

  //把大小按照BYTES_PER_WORD 对齐。BYTES_PER_WORD也即处理器的地址单元,陈诚“研究系” 游离于军统、中统的特务集团 - 博文预览,在i32 为32

  if (size & (BYTES_PER_WORD-1)) {

  size += (BYTES_PER_WORD-1);

  size &= ~(BYTES_PER_WORD-1);

  }

  //如果size 大于1/8 个页面。就把slab放到缓存区的外面

  if (size >= (PAGE_SIZE>>3))

  flags |= CFLGS_OFF_SLAB;

  //使size按照align对齐

  size = ALIGN(size, align);

  if ((flags & SLAB_RECLAIM_ACCOUNT) && size <= PAGE_SIZE) {

  cachep->gfporder = 0;

  cache_estimate(cachep->gfporder, size, align, flags,心情日志,

  &left_over, &cachep->num);

  } else {

  //在这里,为cache中每个slab的大小以及slab中的对象个数取得一个均衡点

  do {

  unsigned int break_flag = 0;

  cal_wastage:

  //cache_estimate:指定slab的大小后,返回slab中的对像个数

  //以及剩余空间数

  cache_estimate(cachep->gfporder, size,心情日志, align, flags,

  &left_over, &cachep->num);

  if (break_flag)

  break;

  if (cachep->gfporder >= MAX_GFP_ORDER)

  break;

  if (!cachep->num)

  goto next;

  if (flags & CFLGS_OFF_SLAB &&

  cachep->num > offslab_limit) {

  /* This num of objs will cause problems. */

  cachep->gfporder--;

  break_flag++;

  goto cal_wastage;

  }

  /*

  * Large num of objs is good, but v. large slabs are

  * currently bad for the gfp()s.

  */

  if (cachep->gfporder >= slab_break_gfp_order)

  break;

  if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))

  break; /* Acceptable internal fragmentation. */

  next:

  cachep->gfporder++;

  } while (1);

  }

  if (!cachep->num) { //涌现意外,打印出常现的oops过错

  printk("kmem_cache_create: couldn't create cache %s.\n", name);

  kmem_cache_free(&cache_cache, cachep);

  cachep = NULL;

  goto opps;

  }

  使slab大小按照align对齐

  slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)

  + sizeof(struct slab), align);

  if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {

  //假如残余空间足间大,就把slab描写符放到缓存区里面

  flags &= ~CFLGS_OFF_SLAB;

  left_over -= slab_size;

  }

  if (flags & CFLGS_OFF_SLAB) {

  //如果slab描述符仍然只能放到缓存区外面。则取slab_size大小的实际值

  //也就是说不需要与alin 对齐了

  slab_size = cachep->num*sizeof(kmem_bufctl_t)+sizeof(struct slab);

  }

  //着色偏移量,至少为一个cache_size.若align值是本人指定的,且超越了一个cache size.这样//值就会取设定的align

  cachep->colour_off = cache_line_size();

  if (cachep->colour_off < align)

  cachep->colour_off = align;

  //色彩的总数,为剩余的空间数/着色偏移量

  //从这里我们可以看到,如果偏移量太少,着色机制是没有任何意思的

  //这是值得提示的是colour_next没有被特殊赋值,即为默认值0

  cachep->colour = left_over/cachep->colour_off;

  //各种成员的初始化

  cachep->slab_size = slab_size;

  cachep->flags = flags;

  cachep->gfpflags = 0;

  if (flags & SLAB_CACHE_DMA)

  cachep->gfpflags |= GFP_DMA;

  spin_lock_init(&cachep->spinlock);

  cachep->objsize = size;

  /* NUMA */

  INIT_LIST_HEAD(&cachep->lists.slabs_full);

  INIT_LIST_HEAD(&cachep->lists.slabs_partial);

  INIT_LIST_HEAD(&cachep->lists.slabs_free);

  //如果slab描述符是放在缓存区外面的。那就为slab描述符指定一个分配缓存

  if (flags & CFLGS_OFF_SLAB)

  cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);

  cachep->ctor = ctor;

  cachep->dtor = dtor;

  cachep->name = name;

  /* Don't let CPUs to come and go */

  lock_cpu_hotplug();

  //g_cpucache_up:断定普通缓存是否停当的标志

  //NONE是初始值 PARTIAL:是一个旁边的状况,即普通缓存正在初始化

  //FULL:一般缓存已经初始化实现

  if (g_cpucache_up == FULL) {

  enable_cpucache(cachep);

  } else {

  if (g_cpucache_up == NONE) { /* Note: the first kmem_cache_create must create * the cache that's used by kmalloc(24), otherwise * the creation of further caches will BUG(). */ cachep->array[smp_processor_id()] = &initarray_generic.cache; g_cpucache_up = PARTIAL; } else { cachep->array[smp_processor_id()] = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL); } BUG_ON(!ac_data(cachep)); ac_data(cachep)->avail = 0; ac_data(cachep)->limit = BOOT_CPUCACHE_ENTRIES; ac_data(cachep)->batchcount = 1; ac_data(cachep)->touched = 0; cachep->batchcount = 1; cachep->limit = BOOT_CPUCACHE_ENTRIES; cachep->free_limit = (1+num_online_cpus())*cachep->batchcount + cachep->num; } cachep->lists.next_reap = jiffies + REAPTIMEOUT_LIST3 + ((unsigned long)cachep)%REAPTIMEOUT_LIST3; //查看是否有雷同名字的cache

  down(&cache_chain_sem);

  {

  struct list_head *p;

  mm_segment_t old_fs;

  old_fs = get_fs();

  set_fs(KERNEL_DS);

  list_for_each(p, &cache_chain) {

  kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);

  char tmp;

  /*

  * This happens when the module gets unloaded and

  * doesn't destroy its slab cache and noone else reuses

  * the vmalloc area of the module. Print a warning.

  */

  #ifdef CONFIG_X86_UACCESS_INDIRECT

  if (__direct_get_user(tmp,pc->name)) {

  #else

  if (__get_user(tmp,pc->name)) {

  #endif

  printk("SLAB: cache with size %d has lost its "

  "name\n", pc->objsize);

  continue;

  }

  if (!strcmp(pc->name,name)) {

  printk("kmem_cache_create: duplicate "

  "cache %s\n",name);

  up(&cache_chain_sem);

  unlock_cpu_hotplug();

  BUG();

  }

  }

  set_fs(old_fs);

  }

  //将cache挂至cache_chain链

  list_add(&cachep->next, &cache_chain);

  up(&cache_chain_sem);

  unlock_cpu_hotplug();

  opps:

  if (!cachep && (flags & SLAB_PANIC))

  panic("kmem_cache_create(): failed to create slab `%s'\n",

  name);

  return cachep;

  }

  首先我们碰到的问题是第一个鸡与鸡蛋的问题:新建cache描述符是从cache_cache中分配cache描述符,那cache_cache是从何而来呢?cache_cache是静态定义的一个数据结构,只要静态初始化它的成员就可以了。另一个鸡与鸡蛋的问题就是cache中array数组的初始化问题。例如:

  cachep->array[smp_processor_id()] =

  kmalloc(sizeof(struct arraycache_init),

  GFP_KERNEL);

  也就是说从普通缓存中分得空间,那普通缓存区中的arry如何取得空间呢?这也是一个静态定义的数组:initarray_generic.cache。我们当前再详细分析内存各子系统的初始化进程。详情请关注本站更新。

  另外,我们也接触到了着色部份的代码。如下所示:

  cachep->colour_off = cache_line_size();

  if (cachep->colour_off < align)

  cachep->colour_off = align;

  cachep->colour = left_over/cachep->colour_off;

  着色的原理在前面已经分析过了。Colour_off:每一个slab中偏移值。以colour:颜色的总数,即最大的偏移位置,它的大小为剩余大小/偏移值,colour_next初始化为零。

  举例阐明:

  Colour_off = 32 colour = 2; colour_next = 0

  第一个slab偏移colour_next* Colour_off = 0*32 = 0 然后colour_next加1。即为1 第二个slab偏移colour_next* Colour_off = 1*32 = 32然后colour_next加1。即为2

  第三个slab偏移colour_next* Colour_off = 2*32 = 64而后colour_next加1。即为3,因为colour为2。所以,colour_next = 0;

  第四个slab偏移colour_next* Colour_off = 0*32 = 0

  ……

  另外:要注意的是slab大小计算的时候:

  slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)+ sizeof(struct slab), align);

  固然在struct slab里不定义kmem_bufctl_t.但在为slab申请空间的时候申请了num个kmem_bufctl_t的过剩空间,也就是说kmem_bufctl_t数组紧放在slab描述符之后

  此外,"I am sure I've been as happy as a clam these last six years,array被初始化了arraycache_init大小。

  struct arraycache_init {

  struct array_cache cache;

  void * entries[BOOT_CPUCACHE_ENTRIES];

  };

  为什么要这样做?我们在后面再给出分析
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
slab分配器简明分析
linux 3.4.10 内核内存管理源代码分析8:Slab内存分配
linux内核分析笔记----内存管理
Linux内存管理之SLAB分配器
slab源码分析--从slab初始化说起
SLAB
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服