ptmalloc - malloc 终章
是不是觉得我的文章名起得很不好,我也觉得,不过,反正我是程序员,只要变量名、函数名、类名和文件名起得好就行了,其他的,不管。
不过,实话实说,这是关于malloc函数的终章了,malloc其实就是包含了几个重要函数,__libc_malloc -> _int_malloc -> sysmalloc,__libc_malloc和sysmalloc已经全部说过了,_int_malloc也提到部分。这一章讲解的就是_int_malloc里面最后的一部分,也是最长的一部分,是不是感觉看这么多代码很累,是的,反正我复制粘贴我都觉得累。
目录
- _int_malloc的结构
- unknown
- 总结
_int_malloc的结构
_int_malloc 里面的代码按照功能,其实可以分割为以下部分
- 从fast中获取内存块 (ptmalloc - 小小内存的分配和申请)
- 从regular bin (medium size) 中获取内存块 (ptmalloc - 小块内存管理初探)
- code = unknown
- 所有地方都匹配不到适合的,就sysmalloc (ptmalloc - 第一次申请小内存)
unknown就是我们这章要讨论的
诚实预告片,接近要看300+行的代码,没耐心请点关闭。
unknown
先看一下最外层的代码吧
static void *
_int_malloc(mstate av, size_t bytes)
{
/* ... */
/* unknown start */
for (; ; ) {
/* ... */
}
}
别激动!正戏开始了。
unknown part1 - unsort
在释放一块内存的时候,free是不会内存块放进去regular bin里面的,而是会把内存块进入一个叫unsort的双链表里面,关于free的详解,以后会说到,现在先说说unsort。
/* ... */
/* 如果unsort里面的back指针不等于自身,说明有未排序的内存块在 */
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) {
bck = victim->bk;
size = chunksize (victim);
/* 如果申请的内存是小内存 */
if (in_smallbin_range (nb) &&
/* 并且unsort里面也只剩下一块内存了 */
bck == unsorted_chunks (av) &&
/* 当前块在分配小内存时被分割过,具体设置下面有 */
victim == av->last_remainder &&
/* 从sort里面拿出来的那块内存大于申请的 */
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
/* 将unsort中获取的那块分割成两半 */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
/* 将分隔剩下的那块取代就有的那块链回去unsort */
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
/* 与大块内存相关的指针设置 */
if (!in_smallbin_range (remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
/* 设置获取到的内存块的头部参数 */
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
/* 设置分割剩下的内存块的头部参数 */
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
return chunk2mem (victim);
}
/* ... */
}
/* ... */
能碰到unsort里面只剩下一块内存的情况就是,要么是只放了一块,要么是跑了那么多个循环没碰到适合的,也只是剩下一块内存了,这两种情况下,好好的使用这块内存,分割下,另外的存起来也是无可厚非,你也可以当成一种垂死挣扎,快点脱离循环的一种渴望。
/* 把victim从unsort中除名 */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* 如果刚刚好victim的size等于申请的 */
if (size == nb) {
set_inuse_bit_at_offset (victim, size);
return chunk2mem (victim);
}
上上面的看看是否能够分割剩下的那块内存是垂死挣扎,那么到了这里才是步入正规
if (in_smallbin_range (size)) {
/* 小块内存就好办了,直接链上对应的bin */
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
} else {
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* 当前链表是否为空 */
if (fwd != bck) {
size |= PREV_INUSE;
/* large bin的是按照大小排序的,恰好,bck->bk就是size最小那一块 */
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) {
fwd = bck;
bck = bck->bk;
/* 链上size链 */
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
} else {
/* 找最小 */
while ((unsigned long) size < chunksize_nomask (fwd)) {
fwd = fwd->fd_nextsize;
}
/*
如果恰好相等,那我就不放上size链了,从当前代码来看
size只是大小的排序,有一个就好了
*/
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) {
/* Always insert in the second position. */
fwd = fwd->fd;
} else {
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else {
/* 初始化当前large bin的size双链 */
victim->fd_nextsize = victim->bk_nextsize = victim;
}
}
/* 标记当前bin已使用,具体下面章节会说到 */
mark_bin (av, victim_index);
/* 链上链表 */
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
/* 如果处理了10000块unsort还是没有心仪的,那就走吧,不挽留了 */
if (++iters >= MAX_ITERS)
break;
}
看了上面的代码,是不是觉得,好像还是挺容易理解的,心想,很快就可以完结malloc的代码了
hlt,想太多了
看过我之前文章的其实应该还记得,其实每个bin之间是16 byte差距,那么申请的时候,11 byte和12 byte是没差距的,它们最终都会被磨成32 byte。那其实是没差距啊,那为何上面还要排序呢?
unsort第一部分代码记不记得,分割。这就就造成了大块有可能被割裂。这当然是一种节省内存的做法,但另一方面却也增加了代码复杂度。借用以前写英文作文的套路句子。Every coin has two side。
另外,从size链和bin链的关系其实能想到一种数据结构,跳表,一个双层跳表。
unknown part2 - large request
if (!in_smallbin_range (nb)) {
bin = bin_at (av, idx);
#define first(bin) (bin)->fd
/* bin->bk是size最小,fd就是size最大 */
if ((victim = first (bin)) != bin &&
(unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb)) {
victim = victim->bk_nextsize;
/* 用size查找最适合 - best fit */
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* 不挪走当前size的第一块是为了避免重新构造skip list */
if (victim != last (bin) &&
chunksize_nomask (victim) == chunksize_nomask (victim->fd))
victim = victim->fd;
remainder_size = size - nb;
/* 从size链和bin链上取下来,并重新构建skip list */
unlink (av, victim, bck, fwd);
/* 不能再分割的情况 */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset (victim, size);
}
/* 大块分割 */
else {
/* 以下这段代码,估计你看到过很多次了,我就懒得写注释了 */
remainder = chunk_at_offset (victim, nb);
bck = unsorted_chunks (av);
fwd = bck->fd;
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
return chunk2mem (victim);
}
}
unknown part3 - bit map
/* 当前索引找不到,+1 */
++idx;
bin = bin_at (av, idx);
/* 具体这个bit map就是,每一个bin如果有内存块就把对应位设为1 */
unsigned int block = idx2block (idx);
unsigned int map = av->binmap[block];
unsigned int bit = idx2bit (idx);
for (;; ) {
/* 如果当前bit的数值已经大于map,或者bit为0 */
if (bit > map || bit == 0) {
/* 一直搜索,直到某个map里面存在非空bin */
do {
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* 找到被设置的bin的bit */
while ((bit & map) == 0) {
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
victim = last (bin);
/* 如果这个bin是空的,清掉bit位 */
if (victim == bin) {
av->binmap[block] = map &= ~bit;
bin = next_bin (bin);
bit <<= 1;
} else {
size = chunksize (victim);
remainder_size = size - nb;
/* 取下来 */
unlink (av, victim, bck, fwd);
/* 耗尽 */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset (victim, size);
}
/* 分割 */
else {
remainder = chunk_at_offset (victim, nb);
/* 将分割后的块丢到unsort里面去 */
bck = unsorted_chunks (av);
fwd = bck->fd;
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* 如果请求是小块内存,把被分割设置成最后被分割块 */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size)){
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
return chunk2mem(victim);
}
}
到此,unknown部分已经完全讲解完毕。
总结
下回分解!