http://blog.chinaunix.net/uid-26859697-id-5472929.html

前面主要分析了以页为最小单位进行内存分配的伙伴管理算法,这对于内核对内存的管理比较简单,同时较大程度上避免了内存碎片的问题。而实际上对内存的申请却不是每次都申请一个页面的,通常是不规则的,大小不一的,并且远小于一个内存页面的大小,此外更可能会频繁地申请释放这些内存。

明显每次分配小于一个页面的都统一分配一个页面的空间是过于浪费且不切实际的,因此必须充分利用未被使用的空闲空间,同时也要避免过多地访问操作页面分配。基于该问题的考虑,内核需要一个缓冲池对小块内存进行有效的管理起来,于是就有了<span style="-ms-word-wrap: break-word;">slab</span>内存分配算法。每次小块内存的分配优先来自于该内存分配器,小块内存的释放也是先缓存至该内存分配器,留作下次申请时进行分配,避免了频繁分配和释放小块内存所带来的额外负载。而这些被管理的小块内存在管理算法中被视之为&ldquo;对象&rdquo;。

Slab内存分配算法是最先出现的;后来被改进适用于嵌入式设备,以满足内存较少的情况下的使用,该改进后的算法占用资源极少,其被称之为<span style="-ms-word-wrap: break-word;">slob</span>内存分配算法;再后来由于<span style="-ms-word-wrap: break-word;">slab</span>内存分配算法的管理结构较大且设计复杂,再一次被改进简化,而该简化改进的算法称之为<span style="-ms-word-wrap: break-word;">slub</span>内存分配算法。当前<span style="-ms-word-wrap: break-word;">linux</span>内核中对该三种算法是都提供的,但是在编译内核的时候仅可以选择其一进行编译,鉴于<span style="-ms-word-wrap: break-word;">slub</span>比<span style="-ms-word-wrap: break-word;">slab</span>分配算法更为简洁和便于调试,在<span style="-ms-word-wrap: break-word;">linux 2.6.22</span>版本中,<span style="-ms-word-wrap: break-word;">slub</span>分配算法替代了<span style="-ms-word-wrap: break-word;">slab</span>内存管理算法的代码。此外,虽然该三种算法的实现存在差异,但是其对外提供的<span style="-ms-word-wrap: break-word;">API</span>接口都是一样的。

创建一个<span style="-ms-word-wrap: break-word;">slab</span>的<span style="-ms-word-wrap: break-word;">API</span>为:

struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; unsigned long flags, void (*ctor)(void *))

_&mdash;&mdash;其中<span style="-ms-word-wrap: break-word;">name</span>为<span style="-ms-word-wrap: break-word;">slab</span>的名称,<span style="-ms-word-wrap: break-word;">size</span>为<span style="-ms-word-wrap: break-word;">slab</span>中每个对象的大小,<span style="-ms-word-wrap: break-word;">align</span>为内存对齐基值,<span style="-ms-word-wrap: break-word;">flags</span>为使用的标志,而<span style="-ms-word-wrap: break-word;">ctor</span>为构造函数;_

销毁<span style="-ms-word-wrap: break-word;">slab</span>的<span style="-ms-word-wrap: break-word;">API</span>为:

void kmem_cache_destroy(struct kmem_cache *s)

从分配算法中分配一个对象:

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

将对象释放到分配算法中:

void kmem_cache_free(struct kmem_cache *cachep, void *objp)

除此之外,该分配算法还内建了一些<span style="-ms-word-wrap: break-word;">slab</span>(<span style="-ms-word-wrap: break-word;">2^KMALLOC_SHIFT_LOW &ndash; 2^KMALLOC_SHIFT_HIGH</span>)用于不需要特殊处理的对象分配。除了上面列举的分配和释放对象的接口,实际上对用户可见的接口为<span style="-ms-word-wrap: break-word;">kmalloc</span>及<span style="-ms-word-wrap: break-word;">kfree</span>。鉴于<span style="-ms-word-wrap: break-word;">slub</span>分配算法的优势,接下来则不对<span style="-ms-word-wrap: break-word;">slab</span>进行深入分析,主要分析<span style="-ms-word-wrap: break-word;">slub</span>分配算法。

首先鸟瞰全局,由下图进行入手分析<span style="-ms-word-wrap: break-word;">slub</span>内存分配算法的管理框架:

<span style="-ms-word-wrap: break-word;">![](http://blog.chinaunix.net/attachment/201511/17/26859697_14477753059O4F.png)</span>

&nbsp;

沿用<span style="-ms-word-wrap: break-word;">slab</span>算法对对象及对象缓冲区的称呼,一个<span style="-ms-word-wrap: break-word;">slab</span>表示某个特定大小空间的缓冲片区,而片区里面的一个特定大小的空间则称之为对象。

Slub内存分配算法中,每种<span style="-ms-word-wrap: break-word;">slab</span>的类型都是由<span style="-ms-word-wrap: break-word;">kmem_cache</span>类型的数据结构来描述的。该结构的定义为:


1. 【file:/include/linux/slub_def.h】
2. /
3.   Slab cache management.
4.  /
5. struct kmem_cache {
6.     struct kmem_cache_cpu __percpu cpu_slab; /每CPU的结构,用于各个CPU的缓存管理/
7.     / Used for retriving partial slabs etc /
8.     unsigned long flags; /描述标识,描述缓冲区属性的标识/
9.     unsigned long min_partial; //
10.     int size; / The size of an object including meta data / /对象大小,包括元数据大小/
11.     int object_size; / The size of an object without meta data / /slab对象纯大小/
12.     int offset; / Free pointer offset. / /空闲对象的指针偏移/
13.     int cpu_partial; / Number of per cpu partial objects to keep around / /每CPU持有量/
14.     struct kmem_cache_order_objects oo; /存放分配给slab的页框的阶数(高16位)
15.                                                  slab中的对象数量(低16位)/
16.     / Allocation and freeing of slabs /
17.     struct kmem_cache_order_objects max;
18.     struct kmem_cache_order_objects min; /存储单对象所需的页框阶数及该阶数下可容纳的对象数/
19.     gfp_t allocflags; / gfp flags to use on each alloc / /申请页面时使用的GFP标识/
20.     int refcount; / Refcount for slab cache destroy / /缓冲区计数器,当用户请求创建新的缓冲区时,SLUB分配器重用已创建的相似大小的缓冲区,从而减少缓冲区个数/
21.     void (ctor)(void ); /创建对象的回调函数/
22.     int inuse; / Offset to metadata / /元数据偏移量/
23.     int align; / Alignment / /对齐值/
24.     int reserved; / Reserved bytes at the end of slabs /
25.     const char name; / Name (only for / /slab缓存名称/
26.     struct list_head list; / List of slab caches / /用于slab cache管理的链表/
27. #ifdef CONFIG_SYSFS
28.     struct kobject kobj; / For sysfs /
29. #endif
30. #ifdef CONFIG_MEMCG_KMEM
31.     struct memcg_cache_params memcg_params;
32.     int max_attr_size; / for propagation, maximum size of a stored attr /
33. #endif
34.  
35. #ifdef CONFIG_NUMA
36.     /
37.       Defragmentation by allocating from a remote node.
38.      /
39.     int remote_node_defrag_ratio; /远端节点的反碎片率,值越小则越倾向从本节点中分配对象/
40. #endif
41.     struct kmem_cache_node node[MAX_NUMNODES]; /各个内存管理节点的slub信息/
42. };
&nbsp;

上面各个成员的意义,可以参考里面的注释,而这里主要关注的是<span style="-ms-word-wrap: break-word;">cpu_slab</span>和<span style="-ms-word-wrap: break-word;">node</span>成员。

其中<span style="-ms-word-wrap: break-word;">cpu_slab</span>的结构类型是<span style="-ms-word-wrap: break-word;">kmem_cache_cpu</span>,是每<span style="-ms-word-wrap: break-word;">CPU</span>类型数据,各个<span style="-ms-word-wrap: break-word;">CPU</span>都有自己独立的一个结构,用于管理本地的对象缓存。具体的结构定义如下:


1. 【file:/include/linux/slub_def.h】
2. struct kmem_cache_cpu {
3.     void freelist; / Pointer to next available object / / 空闲对象队列的指针 /
4.     unsigned long tid; / Globally unique transaction id / / 用于保证cmpxchg_double计算发生在正确的CPU上,并且可作为一个锁保证不会同时申请这个kmem_cache_cpu的对象 /
5.     struct page page; / The slab from which we are allocating / / 指向slab对象来源的内存页面 /
6.     struct page partial; / Partially allocated frozen slabs / / 指向曾分配完所有的对象,但当前已回收至少一个对象的page /
7. #ifdef CONFIG_SLUB_STATS
8.     unsigned stat[NR_SLUB_STAT_ITEMS];
9. #endif
10. };
&nbsp;

至于<span style="-ms-word-wrap: break-word;">kmem_cache_node</span>结构:


1. 【file:/mm/slab.h】
2. /
3.   The slab lists for all objects.
4.  /
5. struct kmem_cache_node {
6.     spinlock_t list_lock; /保护结构内数据的自旋锁/
7.  
8. #ifdef CONFIG_SLAB
9.     struct list_head slabs_partial; / partial list first, better asm code /
10.     struct list_head slabs_full;
11.     struct list_head slabs_free;
12.     unsigned long free_objects;
13.     unsigned int free_limit;
14.     unsigned int colour_next; / Per-node cache coloring /
15.     struct array_cache shared; / shared per node /
16.     struct array_cache alien; / on other nodes /
17.     unsigned long next_reap; / updated without locking /
18.     int free_touched; / updated without locking /
19. #endif
20.  
21. #ifdef CONFIG_SLUB
22.     unsigned long nr_partial; /本节点的Partial slab的数目/
23.     struct list_head partial; /Partial slab的双向循环队列/
24. #ifdef CONFIG_SLUB_DEBUG
25.     atomic_long_t nr_slabs;
26.     atomic_long_t total_objects;
27.     struct list_head full;
28. #endif
29. #endif
30.  
31. };
&nbsp;

该结构是每个<span style="-ms-word-wrap: break-word;">node</span>节点都会有的一个结构,主要是用于管理<span style="-ms-word-wrap: break-word;">node</span>节点的<span style="-ms-word-wrap: break-word;">slab</span>缓存区。

Slub分配管理中,每个<span style="-ms-word-wrap: break-word;">CPU</span>都有自己的缓存管理,也就是<span style="-ms-word-wrap: break-word;">kmem_cache_cpu</span>数据结构管理;而每个<span style="-ms-word-wrap: break-word;">node</span>节点也有自己的缓存管理,也就是<span style="-ms-word-wrap: break-word;">kmem_cache_node</span>数据结构管理。

对象分配时:

1、 当前<span style="-ms-word-wrap: break-word;">CPU</span>缓存有满足申请要求的对象时,将会首先从<span style="-ms-word-wrap: break-word;">kmem_cache_cpu</span>的空闲链表<span style="-ms-word-wrap: break-word;">freelist</span>将对象分配出去;

2、 如果对象不够时,将会向伙伴管理算法中申请内存页面,申请来的页面将会先填充到<span style="-ms-word-wrap: break-word;">node</span>节点中,然后从<span style="-ms-word-wrap: break-word;">node</span>节点取出对象到<span style="-ms-word-wrap: break-word;">CPU</span>的缓存空闲链表中;

3、 如果原来申请的<span style="-ms-word-wrap: break-word;">node</span>节点<span style="-ms-word-wrap: break-word;">A</span>的对象,现在改为申请<span style="-ms-word-wrap: break-word;">node</span>节点<span style="-ms-word-wrap: break-word;">B</span>的,那么将会把<span style="-ms-word-wrap: break-word;">node</span>节点<span style="-ms-word-wrap: break-word;">A</span>的对象释放后再申请。

对象释放时:

1、 会先将对象释放到<span style="-ms-word-wrap: break-word;">CPU</span>上面,如果释放的对象恰好与<span style="-ms-word-wrap: break-word;">CPU</span>的缓存来自相同的页面,则直接添加到列表中;

2、 如果释放的对象不是当前<span style="-ms-word-wrap: break-word;">CPU</span>缓存的页面,则会吧当前的<span style="-ms-word-wrap: break-word;">CPU</span>的缓存对象放到<span style="-ms-word-wrap: break-word;">node</span>上面,然后再把该对象释放到本地的<span style="-ms-word-wrap: break-word;">cache</span>中。

为了避免过多的空闲对象缓存在管理框架中,<span style="-ms-word-wrap: break-word;">slub</span>设置了阀值,如果空闲对象个数达到了一个峰值,将会把当前缓存释放到<span style="-ms-word-wrap: break-word;">node</span>节点中,当<span style="-ms-word-wrap: break-word;">node</span>节点也过了阀值,将会把<span style="-ms-word-wrap: break-word;">node</span>节点的对象释放到伙伴管理算法中。

这里只是做了简单的概要说明,而实际上由于面对的场景复杂,所以实现上有更多值得品味的处理细节。