当前位置: 首页 >> 开源操作系统 >> Linux内核中内存cache的实现
 

Linux内核中内存cache的实现

作者:      来源:zz     发表时间:2007-05-02     浏览次数:      字号:    

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

1. 前言

kmem_cache是Linux内核提供的快速内存缓冲接口,这些内存块要求是大小相同的,因为分配出的内
存在接口释放时并不真正释放,而是作为缓存保留,下一次请求分配时就可以直接使用,省去了各种
内存块初始化或释放的操作,因此分配速度很快,通常用于大数量的内存块分配的情况,如inode节
点,skbuff头, netfilter的连接等,其实kmalloc也是从kmem_cache中分配的,可通
过/proc/slabinfo文件直接读取cache分配情况。
以下Linux内核代码版本为2.6.19.2, 程序主要出自mm/slab.c文件, 2.4和2.6基本原理差不多,但具
体实现中有了不少变化。

2. slab和page

在介绍kmem_cache之前需要先介绍page和slab这两个定义。众所周知,page是内核中内存基本管理单
位,每个page的内存大小是固定的,对X86机器来说,是4K;slab则是kmem_cache的具体的内存空间
形式,根据cache的对象的大小,每个slab可以有1个page到最多32(128/4)个page;如果cache对象比
一个page的空间小,这个slab中会容纳多个对象以尽可能地利用空间。
struct slab {
// 链表
 struct list_head list;
// 未用空间的偏移
 unsigned long colouroff;
// 具体的内存缓冲区地址
 void *s_mem;  /* including colour offset */
// 每个slab中的正在使用的对象数量
 unsigned int inuse; /* num of objs active in slab */
// 空闲对象
 kmem_bufctl_t free;
 unsigned short nodeid;
};
 
3. 数据结构

kmem_cache数据结构并没有定义在.h的头文件中,在头文件中只是该结构的一个类型定义,因为其他
地方使用kmem_cache时完全不需要知道其内部结构,各接口函数完全封装结构中的信息,这是用C实
现OO编程的常用方式。

/* include/linux/slab.h */
// 这里只是一个类型定义
typedef struct kmem_cache kmem_cache_t;

/* mm/slab.c */
// 在C文件中进行完整的定义

/*
 * struct array_cache
 *
 * Purpose:
 * - LIFO ordering, to hand out cache-warm objects from _alloc
 * - reduce the number of linked list operations
 * - reduce spinlock operations
 *
 * The limit is stored in the per-cpu structure to reduce the data cache
 * footprint.
 *
 */
// 这是每个CPU对应的cache数据
struct array_cache {
 unsigned int avail;
 unsigned int limit;
 unsigned int batchcount;
 unsigned int touched;
 spinlock_t lock;
 void *entry[0]; /*
    * Must have this definition in here for the proper
    * alignment of array_cache. Also simplifies accessing
    * the entries.
    * [0] is for gcc 2.95. It should really be [].
    */
};

/*
 * The slab lists for all objects.
 */
// 这是cache管理的slab的链表
struct kmem_list3 {
// 该链表中slab中既有正在使用的对象,也有空闲对象
 struct list_head slabs_partial; /* partial list first, better asm code */
// 该链表中slab的对象都在使用中
 struct list_head slabs_full;
// 该链表中slab的对象都是空闲的
 struct list_head slabs_free;
// 空闲的对象数
 unsigned long free_objects;
// 空闲的限值,超过就该释放掉一些了
 unsigned int free_limit;
 unsigned int colour_next; /* Per-node cache coloring */
 spinlock_t list_lock;
 struct array_cache *shared; /* shared per node */
 struct array_cache **alien; /* on other nodes */
 unsigned long next_reap; /* updated without locking */
 int free_touched;  /* updated without locking */
};

struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
// 每个CPU对应的cache数组
 struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
// 没有空闲对象时为处理器一次批量分配的对象数量
 unsigned int batchcount;
// 在将缓冲池中一半空闲对象释放到全局缓冲池前缓冲池中允许的空闲对象的数量
 unsigned int limit;
 unsigned int shared;
//
 unsigned int buffer_size;
/* 3) touched by every alloc & free from the backend */
// MAX_NUMNODES个cache节点链表,MAX_NUMNODES是编译内核时定义的
 struct kmem_list3 *nodelists[MAX_NUMNODES];
 unsigned int flags;  /* constant flags */
// 每个slab中的对象数
 unsigned int num;  /* # of objs per slab */
/* 4) cache_grow/shrink */
 /* order of pgs per slab (2^n) */
// 表明在内存页中的slab块的大小, 如果对象大小小于4K,该值为1
// 超过4K,该值为slab大小相对4K的倍数, 如对于32K, 该值为8
 unsigned int gfporder;
 /* force GFP flags, e.g. GFP_DMA */
 gfp_t gfpflags;
 size_t colour;   /* cache colouring range */
 unsigned int colour_off; /* colour offset */
 struct kmem_cache *slabp_cache;
 unsigned int slab_size;
 unsigned int dflags;  /* dynamic flags */
 /* constructor func */
// cache构造函数
 void (*ctor) (void *, struct kmem_cache *, unsigned long);
 /* de-constructor func */
// cache析构函数
 void (*dtor) (void *, struct kmem_cache *, unsigned long);
/* 5) cache creation/removal */
// cache的名称
 const char *name;
// cache链表中的下一项
 struct list_head next;
/* 6) 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;
 unsigned long node_allocs;
 unsigned long node_frees;
 unsigned long node_overflow;
 atomic_t allochit;
 atomic_t allocmiss;
 atomic_t freehit;
 atomic_t freemiss;
#endif
#if DEBUG
 /*
  * If debugging is enabled, then the allocator can add additional
  * fields and/or padding to every object. buffer_size contains the total
  * object size including these internal fields, the following two
  * variables contain the offset to the user object and its size.
  */
 int obj_offset;
 int obj_size;
#endif
};

内核cache的管理链表本身也是一个cache, 因此定义了一个静态的cache结构作为这个cache链表的链
表头:
/* internal cache of cache description objs */
static struct kmem_cache cache_cache = {
 .batchcount = 1,
 .limit = BOOT_CPUCACHE_ENTRIES,
 .shared = 1,
 .buffer_size = sizeof(struct kmem_cache),
 .name = "kmem_cache",
#if DEBUG
 .obj_size = sizeof(struct kmem_cache),
#endif
};
/proc/slabinfo就是这个cache链表的基本信息.

关于cache, slab, page的关系可大致表示如下:

    cache <-------------------> cache <--------------------->cache
                                  |
                                  V
                              kmem_list3
                                  |
               +--------------------------------------+
               |                  |                   |
               V                  V                   V
           slab_full         slab_partial         slab_free
               |                  |                   |
               V                  V                   V
             slab               slab                 slab
               |                  |                   |
               V                  V                   V
             page               page                 page
               |                  |                   |
       +-------------+     +--------------+     +-------------+
       |             |     |              |     |             |
       V             V     V              V     V             V
    object  ...  object   object  ...  object object  ...  object
 
4. 操作函数

4.1 基本用法

为使用kmem_cache, 先要用kmem_cache_create函数创建cache, 如:
static kmem_cache_t *ip_conntrack_cachep __read_mostly;
 ip_conntrack_cachep = kmem_cache_create("ip_conntrack",
                                         sizeof(struct ip_conntrack), 0,
                                         0, NULL, NULL);
分配对象空间时使用kmem_cache_alloc函数, 如:
 conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);

释放对象时kmem_cache_free函数, 如:
 kmem_cache_free(ip_conntrack_cachep, conntrack);

模块结束,销毁cache时使用kmem_cache_destroy函数, 如:
 kmem_cache_destroy(ip_conntrack_cachep);
 
4.2 创建cache: kmem_cache_create

该函数创建kmem_cache结构,要提供该cache的名称,每个单元块的大小参数, 其他参数则可以为0或
NULL。
这个函数重点就是根据所需要的内存块大小确定合适的、对齐的slab块大小
/* mm/slab.c */
// name是该cache的名称
// size是cahce中对象的大小, 一般情况下其他参数都可为0或NULL
// align: 指定size要按align对齐
struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,
 unsigned long flags,
 void (*ctor)(void*, struct kmem_cache *, unsigned long),
 void (*dtor)(void*, struct kmem_cache *, unsigned long))
{
 size_t left_over, slab_size, ralign;
 struct kmem_cache *cachep = NULL, *pc;
 /*
  * Sanity checks... these are all serious usage bugs.
  */
// cache名不能为空,不能在中断中分配,每个单元块不能太大,也不能太小
// 如果定义了析构函数不能没有构造函数
 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();
 }
 /*
  * Prevent CPUs from coming and going.
  * lock_cpu_hotplug() nests outside cache_chain_mutex
  */
 lock_cpu_hotplug();
// 锁住cache链表
 mutex_lock(&cache_chain_mutex);
// 循环cache链表,此为全局链表
 list_for_each_entry(pc, &cache_chain, next) {
  mm_segment_t old_fs = get_fs();
  char tmp;
  int res;
  /*
   * This happens when the module gets unloaded and doesn't
   * destroy its slab cache and no-one else reuses the vmalloc
   * area of the module.  Print a warning.
   */
// 检查一下cache是否有效,可能会由于模块的释放却没清除掉
  set_fs(KERNEL_DS);
  res = __get_user(tmp, pc->name);
  set_fs(old_fs);
  if (res) {
   printk("SLAB: cache with size %d has lost its name\n",
          pc->buffer_size);
   continue;
  }
// 相同名称的cache已经有了,出错返回
  if (!strcmp(pc->name, name)) {
   printk("kmem_cache_create: duplicate cache %s\n", name);
   dump_stack();
   goto oops;
  }
 }
// 可以忽略DEBUG中的代码
#if DEBUG
 WARN_ON(strchr(name, ' ')); /* It confuses parsers */
 if ((flags & SLAB_DEBUG_INITIAL) && !ctor) {
  /* No constructor, but inital state check requested */
  printk(KERN_ERR "%s: No con, but init state check "
         "requested - %s\n", __FUNCTION__, name);
  flags &= ~SLAB_DEBUG_INITIAL;
 }
#if FORCED_DEBUG
 /*
  * Enable redzoning and last user accounting, except for caches with
  * large objects, if the increased size would increase the object size
  * above the next power of two: caches with object sizes just above a
  * power of two have a significant amount of internal fragmentation.
  */
 if (size < 4096 || fls(size - 1) == fls(size-1 + 3 * BYTES_PER_WORD))
  flags |= SLAB_RED_ZONE | SLAB_STORE_USER;
 if (!(flags & SLAB_DESTROY_BY_RCU))
  flags |= SLAB_POISON;
#endif
 if (flags & SLAB_DESTROY_BY_RCU)
  BUG_ON(flags & SLAB_POISON);
#endif
 if (flags & SLAB_DESTROY_BY_RCU)
  BUG_ON(dtor);
 /*
  * Always checks flags, a caller might be expecting debug support which
  * isn't available.
  */
 BUG_ON(flags & ~CREATE_MASK);
 /*
  * Check that size is in terms of words.  This is needed to avoid
  * unaligned accesses for some archs when redzoning is used, and makes
  * sure any on-slab bufctl's are also correctly aligned.
  */
// 将对象长度先按BYTES_PER_WORD扩展对齐, 32位机为4字节对齐
 if (size & (BYTES_PER_WORD - 1)) {
  size += (BYTES_PER_WORD - 1);
  size &= ~(BYTES_PER_WORD - 1);
 }
 /* calculate the final buffer alignment: */
// 以下根据函数标志计算实际对齐值
 /* 1) arch recommendation: can be overridden for debug */
 if (flags & SLAB_HWCACHE_ALIGN) {
// 要根据硬件CACHE进行字节对齐,对齐都是2的指数倍
  /*
   * Default alignment: as specified by the arch code.  Except if
   * an object is really small, then squeeze multiple objects into
   * one cacheline.
   */
  ralign = cache_line_size();
  while (size <= ralign / 2)
   ralign /= 2;
 } else {
  ralign = BYTES_PER_WORD;
 }
 /*
  * Redzoning and user store require word alignment. Note this will be
  * overridden by architecture or caller mandated alignment if either
  * is greater than BYTES_PER_WORD.
  */
 if (flags & SLAB_RED_ZONE || flags & SLAB_STORE_USER)
  ralign = BYTES_PER_WORD;
 /* 2) arch mandated alignment: disables debug if necessary */
 if (ralign < ARCH_SLAB_MINALIGN) {
  ralign = ARCH_SLAB_MINALIGN;
  if (ralign > BYTES_PER_WORD)
   flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
 }
 /* 3) caller mandated alignment: disables debug if necessary */
 if (ralign < align) {
// 如果根据系统情况计算出的对齐值小于要求的对齐值,用参数里的对齐值
  ralign = align;
  if (ralign > BYTES_PER_WORD)
   flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
 }
 /*
  * 4) Store it.
  */
// 真正的对齐值
 align = ralign;
 /* Get cache's description obj. */
// 分配cache本身的内存空间,并清零,SLAB_KERNEL标志表明该操作可能会休眠
 cachep = kmem_cache_zalloc(&cache_cache, SLAB_KERNEL);
 if (!cachep)
  goto oops;
#if DEBUG
 cachep->obj_size = size;
 /*
  * Both debugging options require word-alignment which is calculated
  * into align above.
  */
 if (flags & SLAB_RED_ZONE) {
  /* add space for red zone words */
  cachep->obj_offset += BYTES_PER_WORD;
  size += 2 * BYTES_PER_WORD;
 }
 if (flags & SLAB_STORE_USER) {
  /* user store requires one word storage behind the end of
   * the real object.
   */
  size += BYTES_PER_WORD;
 }
#if FORCED_DEBUG && defined(CONFIG_DEBUG_PAGEALLOC)
 if (size >= malloc_sizes[INDEX_L3 + 1].cs_size
     && cachep->obj_size > cache_line_size() && size < PAGE_SIZE) {
  cachep->obj_offset += PAGE_SIZE - size;
  size = PAGE_SIZE;
 }
#endif
#endif
 /*
  * Determine if the slab management is 'on' or 'off' slab.
  * (bootstrapping cannot cope with offslab caches so don't do
  * it too early on.)
  */
 if ((size >= (PAGE_SIZE >> 3)) && !slab_early_init)
// 如果对象大小比较大,设置CFLGS_OFF_SLAB标志
// (PAGE_SIZE >> 3)在X86下是512
  /*
   * Size is large, assume best to place the slab management obj
   * off-slab (should allow better packing of objs).
   */
  flags |= CFLGS_OFF_SLAB;
// 根据算出的对齐长度重新对齐内存单元长度
 size = ALIGN(size, align);
// 计算要分配size大小相对slab大小的阶数,返回每个slab的剩余空间数
 left_over = calculate_slab_order(cachep, size, align, flags);
 if (!cachep->num) {
// cachep->num为每个slab中的对象数
// 为0表示找不到合适的内存slab块大小
  printk("kmem_cache_create: couldn't create cache %s.\n", name);
  kmem_cache_free(&cache_cache, cachep);
  cachep = NULL;
  goto oops;
 }
// 对齐slab结构本身大小, 大小包括slab头(struct slab), 以及cachep->num个对象
// 的控制量的大小, kmem_bufctl_t其实是一个无符合整数
// typedef unsigned int kmem_bufctl_t
 slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t)
     + sizeof(struct slab), align);
 /*
  * If the slab has been placed off-slab, and we have enough space then
  * move it on-slab. This is at the expense of any extra colouring.
  */
// 有OFF_SLAB标志而且slab剩余空间比slab本身还大
 if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
// 将slab参数移到剩余空间中
  flags &= ~CFLGS_OFF_SLAB;
  left_over -= slab_size;
 }
 if (flags & CFLGS_OFF_SLAB) {
  /* really off slab. No need for manual alignment */
  slab_size =
      cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab);
 }
// 填写cache块的基本信息
// colour_off是根据硬件L1 CACHE元素大小来定
// 是
 cachep->colour_off = cache_line_size();
 /* Offset must be a multiple of the alignment. */
 if (cachep->colour_off < align)
  cachep->colour_off = align;
// colour是指在剩余空间中能用的colour_off偏移值的数量
// 表明能放几个整的L1 CACHE元素
 cachep->colour = left_over / cachep->colour_off;
// slab控制部分大小
 cachep->slab_size = slab_size;
 cachep->flags = flags;
 cachep->gfpflags = 0;
 if (flags & SLAB_CACHE_DMA)
  cachep->gfpflags |= GFP_DMA;
// 实际内存缓冲区大小
 cachep->buffer_size = size;
 if (flags & CFLGS_OFF_SLAB) {
  cachep->slabp_cache = kmem_find_general_cachep(slab_size, 0u);
  /*
   * This is a possibility for one of the malloc_sizes caches.
   * But since we go off slab only for object size greater than
   * PAGE_SIZE/8, and malloc_sizes gets created in ascending order,
   * this should not happen at all.
   * But leave a BUG_ON for some lucky dude.
   */
  BUG_ON(!cachep->slabp_cache);
 }
 cachep->ctor = ctor;
 cachep->dtor = dtor;
 cachep->name = name;
// 建立每个CPU各自的cache数据
 if (setup_cpu_cache(cachep)) {
  __kmem_cache_destroy(cachep);
  cachep = NULL;
  goto oops;
 }
 /* cache setup completed, link it into the list */
// 将新建的cache块挂接到cache链表
 list_add(&cachep->next, &cache_chain);
oops:
 if (!cachep && (flags & SLAB_PANIC))
  panic("kmem_cache_create(): failed to create slab `%s'\n",
        name);
 mutex_unlock(&cache_chain_mutex);
 unlock_cpu_hotplug();
 return cachep;
}
// 该函数可在内核模块中访问
EXPORT_SYMBOL(kmem_cache_create);

[1] [2]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •