目录
上一章讲解了ptmalloc的内存的分配器状态机malloc_state的实现。分配器状态机主要管理内存分配过程中的各种状态以及空闲内存的管理。
ptmalloc的最小内存组织单元是malloc_chunk的数据结构。通过malloc_chunk的数据结构,用于管理每次前端程序使用malloc函数调用所产生的在堆上分配的内存。
一、malloc_chunk的数据结构
1. malloc_chunk的基础结构
先来看一下malloc_chunk的数据结构:
- mchunk_prev_size:该字段记录物理相邻的前一个chunk的大小(低地址chunk)。如果前一个chunk处于空闲,则该字段记录前一个chunk大小;如果前一个chunk已经被使用,则该字段空间可以被前一个chunk的用户数据空间复用
- mchunk_size:该字段是chunk的大小。该字段的低三个比特位对 chunk 的大小没有影响,所以被复用为标志位.
- fd和bk:当chunk空闲的时候,会放置到bins上双向链表管理。fd 指向下一个(非物理相邻)空闲的 chunk。bk 指向上一个(非物理相邻)空闲的 chunk。由于只有chunk空闲的时候,才会放置到bins上进行空闲管理,所以fd和bk占用的是用户数据区域user data
- fd_nextsize和bk_nextsize:用于管理large块的时候的空闲chunk双向链表的管理。一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历,也是复用用户数据区域。large chunk的空间肯定装的下
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* 当前chunk的大小 Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
2. malloc_chunk使用时的状态
当前chunk如果被使用中,则有几个特征:
- 复用相邻下一个nextchunk(高地址chunk)的mchunk_prev_size字段的数据空间。所以该chunk的内存空间为当前内存 + mchunk_prev_size字段空间
- 由于chunk被使用中,所以不需要通过双向链表方式挂载到空闲bins上管理,所以fd和bk以及fd_nextsize和bk_nextsize不需要被使用
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
图解:
- chunk-> 是当前内存组织单元的开始地址;
- mem -> 是用户数据区user data的开始区域;
- nextchunk-> 是下一个内存组织单元的开始地址
从上图中可以看到,chunk正在被使用中,所以没有fd/bk以及fd_nextsize/bk_nextsize的指针。
而chunk的用户内存区域(user data),除了本身内存区域之外,也将底部nextchunk的mchunk_prev_size指针数据部分给复用了进来,实现了内存空间的复用。
3. malloc_chunk空闲时的状态
当chunk为空闲的时候,chunk会被挂载的空闲链表bins上进行管理,所以空闲的chunk有几个特征
- fd/bk以及fd_nextsize/bk_nextsize的指针地址,复用了用户数据区域(userdata)的空间
- 下一个nextchunk的mchunk_prev_size值,记录了当前chunk的大小
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
从上图中可以看到,由于空闲chunk需要通过双向链表指针来管理,所以fd/bk的指针复用了用于内存存储区域。
二、mchunk_size的内存分配大小
最小的空间:mchunk_prev_size字段 + mchunk_size字段 + fd字段 + bk字段 所需要的空间。但是chunk的使用时只使用mchunk_prev_size字段 + mchunk_size字段,空闲的时候 fd字段 + bk字段(fd_nextsize/bk_nextsize)用用户空间来替代。所以chunk主要存储mchunk_prev_size/mchunk_size,为16字节(64位/32位系统=8字节)。
chunk的size:(chunk的mchunk_size字段空间SIZE_SZ + 用户数据区域)& 对齐字节(2*SIZE_SZ) ;由于User空间可以复用下一个chunk的mchunk_prev_size,所以可以去除这个空间
chunk对齐规则:按照2*SIZE_SZ进行对齐,32位系统是8字节,64位系统是16字节。
bins步长规则:按照等差数列的方式步长拆分,前一章有详细讲解。以32位系统为例,前64个bin步长8,后32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144。
我们看一下chunk的数据结构大小的图(64位系统):
三、mchunk_size的标记位的复用
mchunk_size字段的最后三个bit位,复用用作了(AMP)的标记位置。后三位bit位的复用,不会影响size的数据大小
- A:A=0 为主分区分配,A=1 为非主分区分配,参见后面
- M:M=1表示使用mmap映射区域,M=0为使用heap区域
- P:P=1 表示pre_chunk空闲,mchunk_prev_size才有效
四、malloc_chunk的宏函数操作
malloc.c文件中定义了malloc_chunk的一系列相关宏的操作,重要定义如下:
- chunk2mem:chunk的起始地址转换到用户内存mem地址。chunk起始地址在低地址,所以通过加上2*SIZE_SZ的方式,转换到高地址的mem地址指针
- mem2chunk:用户内存mem地址转换到chunk的起始地址。用户内存mem地址在高地址,所以通过减去2*SIZE_SZ的方式,转到低地址的chunk的起始地址
- MIN_CHUNK_SIZE:最小的chunk大小。通过offsetof 函数计算出 fd_nextsize 在 malloc_chunk 中的偏移,至少包含mchunk_prev_size、mchunk_size、fd、bk四个地址的空间量。所以64位系统,最小是32字节(4*8);32位系统,最小是16字节(4*4)
- aligned_OK:检查内存是否对齐。64位系统按照16字节对齐,32位系统按照8字节对齐
- request2size:通过对齐后,实际chunk的大小。如果内存大小小于MINSIZE,则使用MINSIZE空间;否则通过MALLOC_ALIGN_MASK进行字节对齐。
/* conversion from malloc headers to user pointers, and back */
#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))
/* The smallest possible chunk */
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
/* The smallest size we can malloc is an aligned minimal chunk */
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
/* Check if m has acceptable alignment */
#define aligned_OK(m) (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)
#define misaligned_chunk(p) \
((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem (p)) \
& MALLOC_ALIGN_MASK)
/* pad request bytes into a usable size -- internal version */
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
/* Check if REQ overflows when padded and aligned and if the resulting value
is less than PTRDIFF_T. Returns TRUE and the requested size or MINSIZE in
case the value is less than MINSIZE on SZ or false if any of the previous
check fail. */
static inline bool
checked_request2size (size_t req, size_t *sz) __nonnull (1)
{
if (__glibc_unlikely (req > PTRDIFF_MAX))
return false;
*sz = request2size (req);
return true;
}
/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks. It
must be a power of two at least 2 * SIZE_SZ, even on machines for
which smaller alignments would suffice. It may be defined as larger
than this though. Note however that code and data structures are
optimized for the case of 8-byte alignment. */
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
? __alignof__ (long double) : 2 * SIZE_SZ)