作者:王智通
一、前言
二、简单的内存管理器示例
三、GNU malloc算法
四、Kernel Buddy伙伴系统算法
五、Kernel Slab/Slub高速缓存算法
一、前言
这次课程最初的题目叫《linux内存管理》, 可是写着写着就感觉这个题目起的太大了, VM(virtul memory)是操作系统中最抽象最复杂的子系统, 想通过一次课把它全部讲清楚有点不现实。 所以我把这次课程的名字改成内存管理初探,先讲讲linux内存的分配算法, 后续课程中在陆续涉及内存映射与回收机制。另外,由于本人水平有限, 有讲的不对的地方, 请大家多多扔砖头, 当然为了支持环保, 请扔银行卡。
二、简单的内存管理器示例
c/c++程序员对glibc的malloc在熟悉不过了,大家在平时的开发中都会用到malloc进行内存分配,或者在对malloc进行一层包装, 如xmalloc,在sshd等开源软件中经常会遇见。 那么大家有没有想过glibc的malloc是如何实现的呢。glibc的内存管理器非常复杂, 如果想要通过分析源码来了解内存管理器的实现将会花很多时间。本小节我们将会通过编写一个简陋的内存管理器来替代GNU glibc的内存管理器, 以此学习下基本的内存管理器设计方法。
1、内存管理简介
前面说道VM(virtual memory)是内核中最复杂的子系统, 它复杂是由于要和进程,文件系统,网络这些子系统都有联系。 一个进程在创建的时候会因COW(copy on write)机制来复制父进程的页表,当进程在运行中由于某些代码或数据不经常使用, 还会被OS将这部分数据写入到磁盘中, 这个过程是VM的内存回收机制,它有很多策略来决定回收哪些进程的哪些数据到磁盘上以便来节省内存空间。 当进程又需要那部分数据的时候, 又会由VM把它从磁盘上加载回来, 这些都是与文件系统的交互。但是有一点我们要清楚, 内存是计算机中的一个硬件设备, 它是非常简单的, 不像硬盘,键盘,显示器等等还需要OS来写专门的驱动程序来驱动它。 所以VM管理的本质实际就是管理虚拟地址,VM只需要按照CPU的规则, 将物理地址与虚拟地址建立好关系,真正读写内存的是CPU。VM的任务就是要配合进程,文件系统来管理好这些虚拟地址。 VM中的内存分配就是按照一定的算法将一部分线性地址划分出来给某些kernel代码使用;VM的映射机制本质上就是物理地址与线性地址的转化,只不过要遵循CPU硬件设计上的规则而已;VM的回收不过就是按照一定的规则将某些线性地址对应的内存回收到磁盘而已。 内存管理的入口点有两个, 一个是前面说的内核入口点, 它是内存管理的核心。 一个是应用层的glibc,glibc的内存管理器用来从内核批发一块内存,然后按照一定的规则来管理这块内存, 进程用到内存的时候, 就从这块内存分割处一块内存给进程, 进程不用的时候在把这块内存回收到那块大内存中去。这样做的目的是提高内存分配的效率,所以glibc的内存管理器实际上不是必须的, 进程每次需要内存的时候, 可以自己通过brk()函数来扩展进程的heap区达到内存分配的目的。 glibc其实也是通过brk()来扩展进程的heap区, 只不过它一次会请求很大的内存,以后进程需要内存的时候就从那个大内存中分配。这有点类似大家经常听到的高速缓存的概念。 这里要注意一点进程的堆栈也会进行内存引用, 堆栈区域的内存是由编译器在编译的时候分配的。下面这个图会帮助大家理清上述关系。
————— —————– —————————
| User Space | –> | Glibc | –> | Kernel(buddy & slab)|
————— —————– —————————
glibc充当了一个批发商的作用, c程序在最初运行的时候不是从main函数开始, 而是从glibc的一个入口函数开始, 它会初始化程序的运行环境, 比如拷贝参数,初始化io管理器,初始化内存管理器。 当运行环境都初始化好之后,在跳转到main函数去运行。 程序运行结束后, 在跳转到glibc的结束函数那里, 清理之前初始化的程序运行环境。 初始化内存管理器, 也是初始化所谓的heap区。进程的heap区就是由内存管理器来管理的, 它通过sys_brk系统调用向内核申请一块空闲的内存, 然后将这块内存分成一个个可以维护的块,malloc()函数的作用就是从内存管理器中按照一定的算法选择一个合适的块返回给程序。free()的时候在将这个块归还给内存管理器。 GNU glibc的内存管理器也是这个原理, 只不过它的管理算法很复杂, 本小节我们将自己实现一个简单的内存管理器, 牛刀小试一把,看看内存管理是怎么设计出来的, 然后我们在下面的章节中在看看工业级的内存管理器应该如何设计。
2、数据结构
内存管理器其实就是管理sys_brk分配的进程heap区, 为了简单,我们将heap区分成大小相同的若干块,然后将他们用双向链表连接起来,分配内存的时候只要顺序的寻找一个没被分配的块即可, 那么它的结构就非常的简单了:
| heap |
—————————————————————
| chunk | chunk | chunk | … | chunk |
—————————————————————
chunk的结构如下:
——————————–
| memory | struct heap_chunk |
——————————–
一个chunk分为2部分, 前面指向实际的内存区, 也就是malloc返回的地址, 后面是管理结构,用于将每个chunk连接起来。
struct heap_chunk {
char *chunk_pos;
int inuse;
struct list_head list;
};
chunk_pos指向实际的内存块。 inuse表示chunk是否被分配。list用于双向链表连接。
3、初始化内存块
前面提到内存管理器是由glibc在程序运行的时候初始化的, 它首先调用sys_brk设置进程的heap区,
我们的内存管理器暂时只管理4M内存。
#define HEAP_SIZE (1024 * 1024 * 4)
#define HEAP_CHUNK_STRUCT_SIZE sizeof(struct heap_chunk)
#define DEFAULT_CHUNK_SIZE 128
int w_crt_heap_init(void)
{
void *heap_end;
INIT_LIST_HEAD(&heap_inactive_list);
INIT_LIST_HEAD(&heap_active_list);
heap_base = (void *)brk(0);
heap_end = (void *)brk(heap_base + HEAP_SIZE);
if (!heap_end)
return -1;
heap_chunk_init(heap_base);
return 0;
}
注意我们现在不使用glibc,所以也没法调用glibc的brk函数,brk()函数需要自己包装一下:
static int brk(void *end_data_segment)
{
int ret;
asm(“movl $45, %%eax\n\t”
“movl %1, %%ebx\n\t”
“int $0×80\n\t”
“movl %%eax, %0″
:”=r”(ret):”m”(end_data_segment));
}
通过c内嵌汇编的方式, 给eax寄存器赋值为45, 它是sys_brk的系统调用号。 ebx是第一个参数。
用int $0×80直接对sys_brk进行调用。 其实glibc也是这么来做的, 不过它还有很多复杂的设计。
通过brk()将进程的heap区设置为4m大小。 然后在将这个heap区分割成若干大小的chunk:
void heap_chunk_init(void *heap_base)
{
int idx;
for (idx = 0; idx < CHUNK_NUM; idx += CHUNK_SIZE) {
struct heap_chunk *heap_chunk;
heap_chunk = (struct heap_chunk *)(heap_base +
DEFAULT_CHUNK_SIZE + idx);
heap_chunk->inuse = 0;
heap_chunk->chunk_pos = heap_base + idx;
list_add_tail(&(heap_chunk->list), &heap_inactive_list);
}
}
每个块都被加入到双向链表中。
3、malloc实现
malloc只是简单的遍历链表找到第一个没被分配的chunk, 将它的inuse结构置1后返回。
void *malloc(unsigned int size)
{
struct heap_chunk *s = NULL;
struct list_head *p = NULL;
if (!size || size > DEFAULT_CHUNK_SIZE)
return NULL;
if (list_empty(&heap_inactive_list))
return NULL;
list_for_each(p, (&heap_inactive_list)) {
s = list_entry(p, struct heap_chunk, list);
if (s && !s->inuse) {
s->inuse = 1;
list_del(p);
list_add_tail(p, &heap_active_list);
return s->chunk_pos;
}
}
return NULL;
}
4、free实现
free()只需要把addr对应的chunk块中的inuse清0, 然后加入到链表的末端。FIFO的方式:)
void free(void *addr)
{
struct heap_chunk *s = NULL;
s = container_of(addr + DEFAULT_CHUNK_SIZE, struct heap_chunk, chunk_pos);
s->inuse = 0;
list_del(&(s->list));
list_add_tail(&(s->list), &heap_inactive_list);
}
5、测试
我们这个简单的内存管理器已经写好了, 下面写个程序测试先:
#include “w_crt.h”
int main(int argc, char **argv)
{
char *s;
s = malloc(64);
strcpy(s, “abcd”);
printf(“%s”, s);
free(s);
}
这个内存管理器包含在我自己写的一个c lib中, 所以不需要glibc的头文件, 完整的源代码在附件中。
编译一下:
wzt@program:~/code/crt$ cat Makefile
all: w_crt.a test
w_crt.a:
gcc -c -fno-builtin -nostdlib -fno-stack-protector entry.c fork.c malloc.c printf.c stdio.c string.c
ar -rs w_crt.a malloc.o printf.o stdio.o string.o fork.o
test:
gcc -c -fno-builtin -nostdlib -fno-stack-protector test.c
ld -static -e w_crt_entry entry.o test.o w_crt.a -o test
clean:
rm -f test *.o *.a
运行:
wzt@program:~/code/crt$ ./test
abcd
6、总结
通过前面的几个小节我们已经知道如何来写一个简单的内存管理器了, 但是它与工业级的内存管理器还是有很大差距的, 一个优秀的内存管理要有如下的优点:
兼容性 – 遵循posix标准
分配/回收速度 – 这是系统性能的热点
线程安全 – SMP处理器上支持多线程的安全分配
支持缓存 – 为了加快分配速度
三、GNU malloc算法
1、常见的开源内存管理器介绍
Doug Lea Malloc:Doug Lea Malloc实际上是完整的一组分配程序,其中包括Doug Lea的原始分配程序,GNU libc分配程序和ptmalloc。Doug Lea的分配程序加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。ptmalloc是Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的部分详细分析ptamlloc2的源代码实现。
BSD Malloc:BSD Malloc是随4.2 BSD发行的实现,包含在FreeBSD之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size类,这些对象的大小为2的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的size类。这样就提供了一个快速的实现,但是可能会浪费内存。
Hoard:编写Hoard的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。
TCMalloc:(Thread-Caching Malloc)是google开发的开源工具──“google-perftools”中的成员。与标准的Glibc库的malloc相比,TCMalloc在内存的分配上效率和速度要高得多。
上面的描述摘自下《Glibc内存管理-ptmalloc2源码分析》一文, 更多关于内存分配器介绍的知识请参考此文。
2、GNU ptmalloc2简介
GNU glibc使用了ptmalloc2内存管理器, 它是由Wolfram Gloger在Doug Lea的基础上改进来的。ptmalloc2可以理解成pthread malloc, 它提供了多线程之间安全分配内存的能力。
3、数据结构
ptmalloc2的空间结构如下:
|unsorted bin| small bins | large bins |
——————————————-
| |16|24|32|…|512|576|640|…|
——————————————-
| | | |
—- —- —- —-
| | | | | | | |
—- —- —- —-
| |
—- —-
| | | |
—- —-
ptmalloc2将相似大小的chunk用链表链接起来, 这样的链表称为一个bin。ptmalloc2一共维护了128个bin。 前64个bins称为small bins用来分配小内存。 后64个bins称为large bins用来分配大内存。 第一个称为unsorted bin, 相当于cache的功能。
4、分配算法
此处省略N个字, 由于ptmalloc2的内存分配策略有点复杂, 用简短的文字描述不清,在给大家分享的时候, 我会用一些形象的语言来描述, 请大家一定要去听哦。
四、Kernel Buddy伙伴系统算法
Linux kernel的内存分配算法是由Buddy伙伴系统和Slab/Slub/Slob共同来控制的。Buddy是内存管理的最上层结构, 它分配若干个连续的物理内存页, 一个物理页大小为4kb, 也是说通过Buddy系统分配得到的内存最少是4kb,它管理的是大内存块。Slab基于Buddy, 将一个物理内存页分成若干小块进行管理, 所以slab用于管理小块。本小节和接下来的关于slab算法描述的小节不会剖析linux kernel的代码实现,因为这样的paper实在太多了, 大家有需要可以看文后的参考文章。再者应用层程序员也不必理解kernel的内存分配算法具体实现。针对buddy/slab内存分配算法, 我根据linux kernel对buddy和slab实现的原理山寨了一个超小型的buddy和slab内存分配器,麻雀虽小,五脏俱全,它们能很好的工作在我自己写的os中了。os的部分源码提供在附件中(主要是buddy.c和slab.c)。 通过这两个简单的示例代码希望能帮助程序员了解buddy/slab算法的基本原理, 在以后关于cache设计的项目中都可以用到。
1、数据结构
linux kernel buddy用于分配N个连续的物理内存页, 一个物理页4k大小。 为了快速分配, buddy
将空闲物理页划分成了11个链表, 每个链表依次保存1,2,4,8,16,32,64,128,256,512,1024个连续的物理页。1024个物理页代表kernel一次最多可以分配4M字节的空闲内存。它的结构如下:
+—-+
|1024|–> …
+—-+
|512 |
+—-+
|256 |
+—-+
|128 |
+—-+
| 64 |
+—-+ +———————————–+
| 32 |–>| PAGE_SIZE*32 | –> …
+—-+ +———————————–+
| 16 |
+—-+
| 8 |
+—-+ +—————————+ +—————————-+
| 4 |–>| PAGE_SIZE*4 |–>| PAGE_SIZE*4 |
+—-+ +—————————+ +—————————-+
| 2 |
+—-+ +———–+ +———–+
| 1 |–>| PAGE_SIZE |–>| PAGE_SIZE |
+—-+ +———–+ +———–+
我们用一个struct mm_buddy数组来管理这些内存块,结构如下:
struct mm_buddy {
int size;
int chunk_num;
int order;
int free_num;
int total_num;
int obj_map[MAX_BUDDY_CHUNK_NUM];
void *obj[MAX_BUDDY_CHUNK_NUM];
};
obj_map和obj用来定为buddy上的一个空闲块,obj保存了buddy上的所有空闲块的地址, obj_map是对应空闲块的标志。
每个内存块由struct mm_buddy_chunk来描述:
struct mm_buddy_chunk {
void *chunk_pos;
int inuse;
struct list_head list;
};
2、初始化:
为了快速定为struct mm_buddy结构, 我们用mm_buddy_array[11]数组来管理所有struct mm_buddy结构。
初始化buddy系统就是对这个数组进行赋值。
void init_buddy_list(void)
{
void *base;
int i, j;
base = mm_buddy_base;
for (i = 0; i < BUDDY_CHUNK_NUM; i++) {
mm_buddy_array[i].size = PAGE_SIZE * (1 << i) * buddy_chunk_num;
mm_buddy_array[i].chunk_num = buddy_size[i];
mm_buddy_array[i].order = i;
mm_buddy_array[i].free_num = buddy_chunk_num;
mm_buddy_array[i].total_num = buddy_chunk_num;
for (j = 0; j < buddy_chunk_num; j++)
mm_buddy_array[i].obj[j] = base + j * (PAGE_SIZE * (1 << i));
for (j = 0; j < MAX_BUDDY_CHUNK_NUM; j++)
mm_buddy_array[i].obj_map[j] = 0;
base += mm_buddy_array[i].size;
}
}
3、分配算法:
假设现在要分配一个连续1个物理页的空闲内存, 那么首先找到数组下标为0的struct mm_buddy结构,然后搜索这个内存块链表, 找到一个还有剩余内存的struct mm_buddy结构, 从中选取一个空闲块返回。 如果数组下标为0的struct mm_buddy结构没有空闲内存了, 这时就逐层向上搜索有剩余的struct mm_buddy结构。假设现在在下标为2的struct mm_buddy结构中找到了还有空闲内存的struct mm_buddy_chunk结构, 它首先将这个块划分一个物理页出来, 然后就要对剩余的3个物理页进行分解。依次划分2个物理页给数组下标1的struct mm_buddy结构, 划分最后1个物理页给下标为0的struct mm_buddy结构。通过算法来达到减少内存碎片的目的, 这个就是伙伴系统的核心算法。
alloc_page函数用来分配2^order数目的连续物理页, 比如order为0表示就分配一个物理页,order为1表示分配2个物理页, order为2,分配4个物理页。 下面是alloc_page函数的核心代码实现, 完整源代码在附件中。释放算法基本就是逆过程, 下文不在讲述, 大家可以参考源代码。
void *alloc_page(int order)
{
int idx;
void *addr;
if (mm_buddy_array[order].free_num) {
addr = alloc_buddy_chunk(order);
return addr;
}
for (idx = order + 1; idx < BUDDY_CHUNK_NUM; idx++) {
if (mm_buddy_array[idx].free_num) {
//printk(“alloc page from order: %d\n”, idx);
addr = __alloc_page(order, idx);
if (addr)
return addr;
}
}
return NULL;
}
void *__alloc_page(int old_order, int new_order)
{
int i, next;
void *base, *new_base, *addr;
for (i = 0; i < mm_buddy_array[new_order].total_num; i++) {
if (!mm_buddy_array[new_order].obj_map[i]) {
mm_buddy_array[new_order].obj_map[i] = 1;
next = i;
break;
}
}
if (next >= mm_buddy_array[new_order].total_num)
return NULL;
mm_buddy_array[new_order].free_num–;
base = addr = mm_buddy_array[new_order].obj[next];
new_base = base + (1 << new_order) * PAGE_SIZE;
while (new_order > old_order) {
new_order–;
new_base -= (1 << new_order) * PAGE_SIZE;
next = ++mm_buddy_array[new_order].total_num;
mm_buddy_array[new_order].obj_map[next] = 0;
mm_buddy_array[new_order].free_num++;
mm_buddy_array[new_order].obj[next - 1] = new_base;
}
return addr;
五、Kernel Slab高速缓存算法
1、数据结构
Kernel Buddy伙伴系统是用来分配连续物理页的,因为内核是管理内存的基本单位是物理页。 如果内核代码想要分配小内存,比如32字节,buddy就没办法提供了。Slab分配算法被用来管理小的内存块。它的基本思想是将一个物理页划分成多个小的obj块, 为了减少内存碎片, slab将每个物理页划分成了大小为
8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096的小块。 slab的结构如下:
——- —— ——
|cache|–> |slab| –> |slab|
——- —— ——
|cache|
—–
|cache| …
—–
|cache| …
—–
|cache| …
——- —— ——
|cache|–> |slab| –> |slab|
——- —— ——
|cache|–>|slab|–>|slab|
——- —— ——
每个cache由struct slab_cache来描述:
struct slab_cache {
struct slab_obj_cache obj_cache; /* 用来实现obj的本地高速缓存 */
void *slab_page; /* 最后一个slab的地址, kfree的时候用到 */
int slab_size; /* slab的大小 */
int slab_num; /* cache中, slab的数目 */
int free_num; /* cache中剩余slab的数目 */
int align; /* 用于CPU高速缓存 */
int color_num; /* slab的颜色, 用于CPU L1 cache */
int color_next; /* 下一个slab的颜色 */
char name[SLAB_CACHE_NAME_LEN]; /* slab的名字 */
void (*ctor)(void); /* cache的初始化函数 */
void (*dtor)(void); /* cache的结束函数 */
struct list_head list; /* 用于链接每个slab */
struct list_head cache_list; /* 用于链接每个cache */
};
一个slab的结构如下:
+———————————————–+
| struct slab | bufctl | obj | obj | …| color |
+———————————————–+
每个slab的最前面是这个slab的管理结构, bufctl是个数组,用于将后面的obj链接起来。obj就是一个要分配的对象, color用于CPU cache对齐。它的结构如下:
struct slab {
int obj_num; /* slab中obj的数目 */
int free_num; /* 剩余obj的数目 */
int free_idx; /* slab中下一个空闲obj的索引 */
void *base; /* slab中第一个obj的地址 */
struct list_head list; /* 用于链接每个slab */
};
2、初始化
先初始化每个cache, cache用slab_cache_array数组来管理。
void init_general_slab_cache(void)
{
int i;
printk(“Init genernal slab cache.\n”);
for (i = 0; i < SLAB_SIZE_NUM; i++) {
slab_cache_array[i].slab_size = slab_size[i];
slab_cache_array[i].slab_num = SLAB_NUM;
slab_cache_array[i].free_num = 0;
slab_cache_array[i].ctor = NULL;
slab_cache_array[i].dtor = NULL;
slab_cache_array[i].color_num =
compute_slab_color_num(slab_size[i], PAGE_SIZE);
slab_cache_array[i].color_next = 0;
slab_cache_array[i].obj_cache.limit = 0;
INIT_LIST_HEAD(&slab_cache_array[i].list);
init_slab(&slab_cache_array[i], slab_size[i]);
}
}
每个cache默认有2个slab, 他们用链表链接起来。
int init_slab(struct slab_cache *slab_cache, int size)
{
int i;
for (i = 0; i < SLAB_NUM; i++) {
void *addr;
// alloc_page是buddy系统提供的api, 用来分配一页物理内存。
addr = alloc_page(PAGE_ORDER_ZERO);
if (!addr) {
printk(“alloc page failed.\n”);
return -1;
}
//printk(“slab alloc page at 0x%x\n”, addr);
// 继续初始化每个slab
__init_slab(slab_cache, addr, size);
}
return 0;
}
void __init_slab(struct slab_cache *slab_cache, void *addr, int size)
{
struct slab *new_slab = (struct slab *)addr;
void *base;
int idx;
// 根据size来计算slab中obj的数目。
new_slab->obj_num = compute_slab_obj_num(size, PAGE_SIZE);
new_slab->free_num = new_slab->obj_num;
// slab_bufctl数组中保存的是下一个空闲的obj索引, 相当于用链表的形式把obj链接起来。
for (idx = 0; idx < new_slab->obj_num – 1; idx++)
slab_bufctl(new_slab)[idx] = idx + 1;
slab_bufctl(new_slab)[idx] = -1;
if (slab_cache->ctor)
slab_cache->ctor();
slab_cache->slab_page = addr;
slab_cache->free_num += new_slab->free_num;
slab_cache->color_next = get_slab_color(slab_cache);
new_slab->free_idx = 0;
list_add_tail(&(new_slab->list), &(slab_cache->list));
new_slab->base = set_slab_base_addr(addr, new_slab);
new_slab->base = fix_slab_base_addr(new_slab->base,
slab_cache->color_next);
}
2、分配算法
先根据size的大小计算对应的slab_cache_array数组下标, 然后遍历slab链表, 找到一个
还有空闲obj的slab。 在通过get_slab_obj函数从中取得一个空闲的obj。
void *get_slab_obj(struct slab *slab, struct slab_cache *slab_cache)
{
void *obj;
obj = slab->base + slab_cache->slab_size * slab->free_idx;
slab->free_idx = slab_bufctl(slab)[slab->free_idx];
slab->free_num–;
slab_cache->free_num–;
return obj;
}
void *kmalloc(int size)
{
struct slab *s = NULL;
struct list_head *p = NULL;
void *obj;
int idx;
if (size < 0 || size > 1024)
return NULL;
idx = check_slab_size(size);
// 当cache中没有空闲的slab时就扩展这个cache, 重新生成一个slab。
if (!slab_cache_array[idx].free_num) {
printk(“expand slab obj in %d.\n”, idx);
if (!(s = expand_slab(&slab_cache_array[idx]))) {
printk(“expand slab failed.\n”);
return NULL;
}
obj = get_slab_obj(s, &(slab_cache_array[idx]));
return obj;
}
// 遍历slab链表找到一个空闲的slab结构。
list_for_each(p, (&slab_cache_array[idx].list)) {
s = list_entry(p, struct slab, list);
if (s && s->free_num) {
// 找到一个空闲的obj。
obj = get_slab_obj(s, &(slab_cache_array[idx]));
return obj;
}
}
return NULL;
}
六、参考文献
1、内存管理内幕 – http://www.ibm.com/developerworks/cn/linux/l-memory/
2、Glibc内存管理-ptmalloc2源码分析 – http://mqzhuang.iteye.com/blog/1005909
3、ptmalloc分配器的分析 – http://blogold.chinaunix.net/u/8059/showart_1002710.html
4、ptmallo2源码 – http://www.malloc.de/en/
5、Linux slab 分配器剖析 – http://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/
6、linux kernel source code – http://www.kernel.org
转 http://blog.aliyun.com/942