Chinaunix首页 | 论坛 | 博客
  • 博客访问: 343079
  • 博文数量: 96
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 152
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-02 09:27
文章分类

全部博文(96)

文章存档

2017年(2)

2016年(30)

2015年(38)

2014年(25)

2013年(1)

我的朋友

分类: LINUX

2016-06-14 09:05:59

原文地址:Linux Slab分配器(一) 作者:wmiss414

一:准备知识:
前面我们分析过了大内存分配的实现机制,事实上,若为小块内存而请求整个页面,这样对于内存来说是一种极度的浪费。因此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又按照空,末满,全满全部链接至高速缓存中.如下图所示:
一:准备知识:
前面我们分析过了大内存分配的实现机制,事实上,若为小块内存而请求整个页面,这样对于内存来说是一种极度的浪费。因此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又按照空,末满,全满全部链接至高速缓存中.如下图所示:
diyblPic
三: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 long free_objects;      /*空链的对象数*/
     int      free_touched;
     unsigned long next_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只需要把%
阅读(1350) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~