内存管理不外乎三个层面,用户管理层,C运行时库层,操作系统层
目前轮子处于运行时库层,制作这个轮子的目的是为了了解底层内存分配是如何实现的,后面肯定主要是实现用户管理层。
常见C内存管理程序:
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”中的成员
目前主要通过阅读GNU的libc的ptmalloc来学习主流的内存管理
malloc_chunk
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
glibc源码中有Colin Plumb作出的很详细的介绍。chunk的使用采用了一个“boundary tag”的方法ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps。重点理解chunk的复用。
一个空闲的块的存储结构如下:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long).
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
每个free的chunk都是保存在一个双向链表,而所有的双向链表都保存在bin中,所以mem中会保存fp和bp。而每个chunk的开头会保存前一个free的chunk的size,紧接着的head会保存chunk的大小,但是最后一位p表示前面一个chunk是否被使用,置0表示没被使用。第一次分配是该位置1。
如果p位置1,则表示前一个chunk被使用了,则size of previou chunk没有任何意义,反而可以被前一个chunk利用来保存数据。fp和bp同样被复用。
则保存内容的块的结构如下:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if allocated| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
在开始有一个声明(对size_t的处理):
#ifndef INTERNAL_SIZE_T
#define INTERNAL_SIZE_T size_t
#endif
...
/* The corresponding word size */
#define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
...
struct malloc_chunk;
typedef struct malloc_chunk* mchunkptr;
紧接着 后面一块内容是对块的size和对齐的一系列处理:
因为malloc之后返回的指针为mem的起始地址,所以有对该地址与chunk地址的转换,由上面的结构表示可以明白下面的处理:
#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))
每个chunk最小的大小:
#define MIN_CHUNK_SIZE(offsetof(struct malloc_chunk, fd_nextsize))
其中
#define offsetof(s, m) (size_t)&(((s *)0)->m)
s是一个结构名,它有一个名为m的成员(s和m 是宏offsetof的形参,它实际是返回结构s的成员m的偏移地址.(s )0 是骗编译器说有一个指向类(或结构)s的指针,其地址值0。&((s )0)->m 是要取得类s中成员变量m的地址. 因基址为0,这时m的地址当然就是m在s中的偏移。最后转换size_t 型。
后面还有一些处理不细讲了,源码中都有很详细的注释。重点是要了解chunk的数据结构以及内存复用的思想。