有了上篇文章的基础,现在可以详细分析malloc()申请内存的流程了,代码如下:
void* dlmalloc(size_t bytes) {
/*
Basic algorithm: 算法描述
If a small request (< 256 bytes minus per-chunk overhead):
1. If one exists, use a remainderless chunk in associated smallbin.
(Remainderless means that there are too few excess bytes to
represent as a chunk.)
2. If it is big enough, use the dv chunk, which is normally the
chunk adjacent to the one used for the most recent small request.
3. If one exists, split the smallest available chunk in a bin,
saving remainder in dv.
4. If it is big enough, use the top chunk.
5. If available, get memory from system and use it
Otherwise, for a large request:
1. Find the smallest available binned chunk that fits, and use it
if it is better fitting than dv chunk, splitting if necessary.
2. If better fitting than any binned chunk, use the dv chunk.
3. If it is big enough, use the top chunk.
4. If request size >= mmap threshold, try to directly mmap this chunk.
5. If available, get memory from system and use it
The ugly goto's here ensure that postaction occurs along all paths.
*/
if (!PREACTION(gm)) {
void* mem;
size_t nb;
// 如果申请的内存量小于244字节,表示是小块内存.
if (bytes <= MAX_SMALL_REQUEST) { // 244字节
bindex_t idx;
binmap_t smallbits;
// 修改申请的内存量,考虑malloc_chunk占用的内存,考虑8字节对齐问题.
nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);
// 根据申请的内存大小计算在small bins中的索引号
idx = small_index(nb);
// 检查对应的链表或相邻链表中是否有空闲内存块
smallbits = gm->smallmap >> idx;
if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */
mchunkptr b, p;
// 如果对应链表为空,就使用相邻链表中的内存块.
idx += ~smallbits & 1; /* Uses next bin if idx empty */
b = smallbin_at(gm, idx); // 取出这条链表
p = b->fd; // 这是链表中第一个空闲的内存块,也正是要分配给应用程序使用的内存块.
assert(chunksize(p) == small_index2size(idx));
unlink_first_small_chunk(gm, b, p, idx); // 将p从链表中摘除
// 对内存块做一些设置
set_inuse_and_pinuse(gm, p, small_index2size(idx));
mem = chunk2mem(p); // 这是返还给应用程序的内存块的指针
check_malloced_chunk(gm, mem, nb); // 这是一个检查函数
goto postaction; // 找到了,返回吧.
}
else if (nb > gm->dvsize) { // 申请的内存量比last remainder要大,那么就不能使用last remainder了.
// 但是其他链表中还有空闲内存块,从其他链表中分配.
if (smallbits != 0) { /* Use chunk in next nonempty smallbin */
// 首先需要做的事情就是在small bins中查找一条合适的链表,这条链表非空,并且与请求的内存量差距最小。
mchunkptr b, p, r;
size_t rsize;
bindex_t i;
binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));
binmap_t leastbit = least_bit(leftbits);
compute_bit2idx(leastbit, i);
b = smallbin_at(gm, i); // b就是找到的链表
p = b->fd; // 这是链表中第一个节点,也就是要分配个应用程序的内存块。
assert(chunksize(p) == small_index2size(i));
unlink_first_small_chunk(gm, b, p, i); // 将这个节点从链表中摘除.
rsize = small_index2size(i) - nb; // 去除我们申请的内存后,这个chunk中剩余的空闲内存量.
/* Fit here cannot be remainderless if 4byte sizes */
if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)
set_inuse_and_pinuse(gm, p, small_index2size(i));
else { // chunk中剩余的内存量至少是8字节,因此可以继续作为一个独立的内存块使用.
set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
r = chunk_plus_offset(p, nb); // 这就是分割nb后剩余的内存构成的新内存块.
set_size_and_pinuse_of_free_chunk(r, rsize);
replace_dv(gm, r, rsize); // 用这个内存块替换掉dv,原先的dv保存在合适的链表中.
}
mem = chunk2mem(p); // 这是返还给用户程序的缓冲区的指针.
check_malloced_chunk(gm, mem, nb);
goto postaction;
} // end if (smallbits != 0)
// small bins中没有空闲内存块了,因此使用tree bins中的内存块.
// 由于这个内存块大于我们请求的内存量,因此将这个内存块划分成两个内存块,
// 一个返回给用户程序使用ÿ