获取中...

-

Just a minute...

_int_malloc()函数是内存分配的核心

malloc会调用 _ _ libc _ malloc 分配内存,_ libc_malloc会调用malloc_hook_ini 进行初始化,然后回调 _ _libc_malloc函数,这时候会执行_int_malloc开始分配内存,定义在malloc.c中。 _ libc_malloc函数是用来封装 int_malloc函数的, _libc_malloc() 函数的工作主要是_int_malloc(), _int_malloc()函数是内存分配的核心

在__libc_malloc函数中,_int_malloc会根据应用层用户申请的内存块的大小,从而分配相应的chunk给用户使用。

1
victim = _int_malloc (ar_ptr, bytes);

定义相关变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void *
_int_malloc (mstate av, size_t bytes)
{
/*定义相关的变量*/
INTERNAL_SIZE_T nb;
unsigned int idx;
mbinptr bin;
mchunkptr victim;
INTERNAL_SIZE_T size;
int victim_index;
mchunkptr remainder; //堆的大小remaindar
unsigned long remainder_size; //remaindar chunk的大小
unsigned int block;
unsigned int bit;
unsigned int map;
mchunkptr fwd; //chunk的fd所指的块
mchunkptr bck; //chunk的bf所指的块
const char *errstr = NULL;

计算 chunk size

1
2
3
4
5
6
7
8
9
/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size traps (returning 0) request sizes
that are so large that they wrap around zero when padded and
aligned.
*/
checked_request2size (bytes, nb);

__libc_malloc 的参数 bytes 是用户提交的最原始的空间大小,但是 ptmalloc 分配时是以 chunk 为单位分配的,由原始的空间大小计算得到 chunk 的空间大小由 checked_request2size 宏完成。首先调用checked_request2size将需要分配的内存大小bytes转换为chunk的大小。nb就是计算得到的chunk的大小。

request2size

1
2
3
4
5
//request2size将内存大小bytes转换为chunk
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

ptmalloc中关于chunk的定义

1
2
3
4
5
6
7
8
9
10
11
struct malloc_chunk {

INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;

struct malloc_chunk* fd;
struct malloc_chunk* bk;

struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

当一个chunk为空闲时,至少要有prev_size、size、fd和bk四个参数,因此MINSIZE就代表了这四个参数需要占用的内存大小.而当一个chunk被使用时,prev_size可能会被前一个chunk用来存储,fd和bk也会被当作内存存储数据,因此当chunk被使用时,只剩下了size参数需要设置,request2size中的SIZE_SZ就是INTERNAL_SIZE_T类型的大小,因此至少需要req+SIZE_SZ的内存大小。MALLOC_ALIGN_MASK用来对齐,因此request2size就计算出了所需的chunk的大小。

传入的参数av是__libc_malloc中调用arena_get获得的分配去指针,如果为null,就表示没有分配区可用,这时候就直接调用sysmalloc通过mmap获取chunk。

检测arena

如果没有可用的arena,或者arena的内存不足,那么就通过系统调用mmap去申请一块内存,并返回s

1
2
3
4
5
6
7
 if (__glibc_unlikely (av == NULL))//如果arean是空,分配堆块
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

fastbin

查看是否命中fastbin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) 
/*通过get_max_fast宏判断是否nb属于fastbins*/
{
idx = fastbin_index (nb); //取到fastbin的起始头索引号
mfastbinptr *fb = &fastbin (av, idx);//取到堆块
/*首先获取到fastbins上相应的fastbin链表,然后获取链表
上第一个块。如果该块不为空,那么更改链表头指向第二个块*/
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))//索引对应的fastbin大小
{
errstr = "malloc(): memory corruption (fast)";//报错退出
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);//检测,freechunk的size最低位是1
return NULL;
}
check_remalloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

得到可以分配的内存之后,首先判断size的大小是否符合fastbin的大小范围,最大的fastbin是通过全局变量global_max_fast来表示的。在这一部分,首先获取size大小在fastbin链表中的表示相应大小的fastbin的链表头指针,即fb与pp。判断当前所在的链表中是否存在相应大小的fastbin chunk。若不存在则跳出fastbin分配函数,去申请相应大小的smallbin。

若链表非空,则分配第一个chunk。注意这里检查了所分配的chunk的size与当前fastbin链表所表示的size的大小是否相同,若不相同则调用malloc_printerr函数打印错误信息。接着调用了check_remalloced_chunk函数对chunk结构体的一些标志位做了检查,主要检查了该chunk是否是malloc_state所表示的分配区中的,检查这块chunk是否已经分配,是否重复分配和一些大小的检查。

判断malloc的大小是否在fastbin范围内

1
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))

寻找可用freechunk

根据malloc的大小,查找相对应大小的fastbin链是否有可用的freechunk。如果有就获取(REMOVE_FB),如果没有就直接退出

1
2
3
4
5
6
7
8
9
10
11
idx = fastbin_index (nb); //取到fastbin的起始头索引号
mfastbinptr *fb = &fastbin (av, idx);//取到堆块
/*首先获取到fastbins上相应的fastbin链表,然后获取链表
上第一个块。如果该块不为空,那么更改链表头指向第二个块*/
mchunkptr pp = *fb;
do
{
victim = pp;
if (victim == NULL)
break;
}

系统检测

取走对应的freechunk之前,还需要进行系统检测

1
2
3
4
5
6
7
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))//索引对应的fastbin大小
{
errstr = "malloc(): memory corruption (fast)";//报错退出
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);//检测,freechunk的size最低位是1
return NULL;
}

alloc_perturb

如果设置了perturb_type,则将获取到的chunk初始化为perturb_type ^ 0xff

1
alloc_perturb (p, bytes);

smallbin

如果在 fastbins 里面没有找到满足要求的空闲 chunk 或者 nb 不属于 fastbins,并且 nb 属于 smallbins,那么执行以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (in_smallbin_range (nb))//申请的堆块大小在smallbin范围内
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)//判断bin链是否为空
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//系统检测
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
/*如果当前的chunk被malloc所使用之后,那么当前
chunk的后面2个chunk的prev_inuse位都要设置为1*/
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

先判断size的大小是否符合smallbin的范围内,若符合small bin的大小范围则在malloc_state结构体中找到与其对应大小的small bin数组的起始地址。接着通过(victim = last (bin)) != bin判断该small bin链表是否为空(从这里可以看出,初始化的small bin的bk指针是指向自身的)

若链表不为空且链表中存储有相应大小的chunk,将链表尾部的chunk分配出去,从分配的链表指针变化我们可以看出,链表头部的bk指针指向的是链表尾部的chunk,链表尾部chunk的fd指针指向链表头部。在分配之前还会确认该chunk的链表指针是否正确,相当于检查了victim->bk->fd!=victim。分配完毕之后会对chunk结构体的标志位做一些检查,这里和fastbin的检查相似。

如果为空则说明获取失败,没有合适的空闲 chunk,直接跳过剩余代码去执行后面的步骤。否则判断获取到的链表尾的 chunk,即代码中的 victim 是否为空,如果为空,说明当前分配区还没有初始化,则 smallbin 还没有被初始化,此时会转至 malloc_consolidate 函数进行初始化后跳过剩余代码。

1
if (__glibc_unlikely (bck->fd != victim))

这里的如果smallbin没有初始化,此时所有的指针均为空,这时程序就会调用malloc_consolidate函数将fastbin链表中的所有的chunk进行合并,并将合并后的chunk插入到unsorted bin中

如果在 smallbins 已经初始化但没有找到合适的空闲 chunk,那么是不会调用 malloc_consolidate 来清空 fastbins 的。

largebin

1
2
3
4
5
6
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}

若不符合small bin的大小,则获取用户申请大小的size所在的large bin数组的下标,并将fastbin进行合并,并插入到unsorted bin链表中。

malloc_consolidate

当申请的大小既不属于fastbins、也不属于smallbins,那么此时就要执行到malloc_consolidate了

1
2
3
4
5
6
7
8
9
10
11
else
{
idx = largebin_index (nb);
/*如果申请的chunk超出了smallbin的范围
就会触发一次unsortedbin的合并整理*/
if (have_fastchunks (av))
/*fastbin中有free和fastchunk的时候才会触发malloc_consolidate*/
malloc_consolidate (av);
/*malloc_consolidate只是将fastbin上的chunk整理到unsortedbin
上,unsortedbin将chunk整理到其他bin链中*/
}

for循环

在fastbins、smallbins中没有找到可用的freechunk可以使用,并且还触发了malloc_consolidate之后就进入for循环了。for循环大致是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
...
}

if (!in_smallbin_range (nb))
{
...
}

for (;; )
{
...
}

use_top:
...

这个for循环大致由四个部分组成
①尝试从unsortedbin中分配用户所需的内存,并进行consolidate(如果成功,可能会产生last remainder)
②如果第一步没有满足需求。尝试从largebins中分配用户所需的内存
③如果第二步没有满足需求。尝试寻找更大的chunk来使用(与第一步一样,也会产生last remainder)
④如果第三步没有满足需求。尝试从top chunk中分配用户所需的内存

当申请的空间满足这四个条件时,就将该唯一一个空闲 chunk 切割之后返回,剩余的空间作为新的 last_remainder 并插入到 unsortedbin 表头。如果切割之后新的 last_remainder 不属于 smallbins,那么要把 fd_nextsize 以及 bk_nextsize 这两个成员给置空。
1.申请空间nb在smallbins范围内。
2.nsortedbin仅有唯一一个空闲chunk。
3.的一个空闲 chunk 是 last_remainder
4.一个空闲 chunk 的大小可以进行切割

大致分析一下这个for循环

unsortedbin

1
2
3
4
5
6
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;

while会一直进行consolidate对chunk进行整理,边整理变查看是否有可以满足需求的chunk可以给malloc使用了,如果有了就return退出了,如果没有,while循环10000次后退出。

unsortedbin的检测

1
2
3
4
5
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

内存损坏机制,检查chunk是否对齐(64位16字节对齐,32位8字节对齐)

判断是否切割last_remainde

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (in_smallbin_range (nb) && //申请的chunk在smallbin的范围内
bck == unsorted_chunks (av) &&
victim == av->last_remainder && //判断是否是last_remainder
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
//切割完剩下的大小不能小于0x20
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
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);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

有四个判断条件:
1.申请空间 nb 在 smallbins 范围内
2.unsortedbin 仅有唯一一个空闲 chunk
3.唯一的一个空闲 chunk 是 last_remainder
4.唯一一个空闲 chunk 的大小可以进行切割

如果这四个条件同时满足,那么就将该唯一一个空闲 chunk 切割之后返回,剩余的空间作为新的 last_remainder 并插入到 unsortedbin 表头。

如果切割之后新的 last_remainder 不属于 smallbins,那么要把 fd_nextsize 以及 bk_nextsize 这两个成员给置空

如果申请的chunk大小为smallbin的大小范围,并且在进入for循环之前在所有的smallbins中没有寻找可用的freechunk。那么进入for循环之后又会再一次申请smallchunk

申请的过程中,如果unsortedbin中有last remainder,并且该last remainder可以切割一块给smallchunk可使用,那么就切割last remainder给malloc的smallchunk使用

set_head、set_foot:如果成功切割某个last remainder给malloc使用,由于last remainder被改变了,所以需要使用者两个宏定义重定设置last remainder的标志位

取走unsortedbin中对应的freechunk(精准匹配则返回)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

如果unsortedbin中有一个freechunk和我们的malloc的请求大小刚好一样,那么直接取走这个freechunk。因为unsortedbin是双链表结构,所以取走freechunk的操作遵循双链表的方法。

consolidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*从这里开始,将unsortedbin中的chunk放入对应的bin链中*/
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
/*如果chunk的大小在smallbin的范围内,放入smallbin中*/
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
/*如果还是chunk的大小在largerbin的范围内,放入largebin中*/
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* 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
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

从unsortedbin中取合适的freechunk给malloc使用之后,接下来就是consolidate了,就是将unsortedbin中的freechunk移动到smallbins或largebins中了

如果某一个freechunk属于smallbin的大小范围,就执行if放入smallbins中。否则该freechunk就属于largechunk,则执行else放入largebins中

插入到 smallbins

如果当前空闲 chunk 不能精确匹配并且属于 smallbins,那么就将其插入到 smallbins 合适的 smallbin 中,插入到链表头部

插入到largebins

插入到 largebins 要麻烦很多,因为 largebins 每一个链表上的 chunk 大小并不是相等的,而是处于一个范围内即可,而且每一个 largebin 并不是一组双向循环链表,而是两组,即 fd 和 bk 构成一个双向循环链表,而 fd_nextsize 和 bk_nextsize 组合构成另一个双向循环链表。因此在插入时不仅寻找合适的插入位置更加复杂,修复链表也多了许多工作

插入到链表头

获取相应 largebin 的链表头 bck 以及第一个空闲 chunk 为 fwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (in_smallbin_range (size))
{
...
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
...
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

当 fwd == bck 时,说明此时 largebin 为空,直接插入到链表头

插入到链表尾

通过 bck->bk 即可找到当前 largebin 的最后一个 chunk,因为在 largebin 中 chunk 按大小从大到小排序,因此最后一个 cunk 也即是最小的一个 chunk。如果待插入 chunk 小于该最小的空闲 chunk,则插入到链表尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (fwd != bck)
{
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
}

插入链表中间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
...
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* 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;
}

插入到链表中间时,首先通过一个 while 循环找到合适的插入位置,注意到这里用的是 fwd = fwd->fd_nextsize,这里就体现了第二组双向循环链表的作用,即可以用于加快寻找合适的插入位置的速度。

找到位置之后判断,当前待插入 chunk 的大小是否和当前位置所在一组相同尺寸的 chunks 的大小相同,如果相同,则插入到该组 chunks 的第二位。注意这里是插入到第二位,因为如果插入成为第一个,就要修改第二组双向循环链表,无疑耗费更多时间。

否则说明当前待插入 chunk 的大小和 largebin 中任何一组相同尺寸的 chunks 的大小都不相同,只能自己插入并成为一组新的相同尺寸的 chunks 的第一个 chunk。

unsortedbin

1
2
3
4
#define MAX_ITERS       10000
if (++iters >= MAX_ITERS)
break;
}

经历了10000此之后,如果还没有找到合适的chunk给malloc使用,那么就不对unsortedbin进行操作了。但是经过这10000之后,unsortedbin中相应的bin都已经放入到对应的bin链中了(smallbins/largebins)

largebin的判断获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/*largebins中有满足要求的freechunk给malloc使用,那么就
执行里面的if语句,切割largebin中的chunk给malloc使用*/
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;

remainder_size = size - nb;
unlink (av, victim, bck, fwd);

如果fastbins、smallbins、unsortedbin中的chunk都不符合malloc的要求,接下来就是判断malloc的chunk是否为largebin,并在largebins中找chunk来使用了

largebin的MINISIZE操作

1
2
3
4
5
6
7
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}

如果在unsortedbin中切割freechunk产生last remainder,如果last remainder大小小于MINISIZE,那么last remainder还会留在unsortedbin上。但是largebin不同,如果对largechunk切割产生last remainder,如果last remainder大小小于MINISIZE,那么剩余的last remainder就也给malloc使用了。(例如malloc需要0x100的chunk,某一个largehunk为0x110,那么切割之后last remainder为0x10,由于0x10 < MINISIZE,那么这0x10也一起给malloc了,那么malloc最后实际上是申请了0x110的堆空间)。所以if会预先判断剩余的大小是否会小于MINISIZE

然后开始切割largechunk给malloc使用,并将切割last remainder放入到unsortedbin中了。同时设置一些标志位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
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);
}

寻找更大的chunk

1
2
3
4
5
6
7
8
9
10
11
12
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
}
while ((map = av->binmap[block]) == 0);

bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}

ptmalloc的主要目的是为了减少程序与内核的交互,提高效率。同时为了减少“野内存”的出现太多,所以当所有的bins链都没有合适的chunk可使用之后,并且再考虑使用topchunk之前,会再次去寻找一个较大的chunk来使用(寻找比当前的bin更大的fastbin、smallbin、largebin来使用)

1
2
3
4
5
6
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}

如果更大的chunk被切割使用之后的last remainder大小小于MINISIZE,那么剩下的last remainder也给malloc使用了。同时也会更新一些标志位

topchunk

如果large bin中不存在符合要求的chunk,那么就需要从topchunk上分配一块符合要求的chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
victim = av->top;
size = chunksize (victim);

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

再次使用malloc_consolidate

当topchunk也不够使用的时候,此时不会直接去调用sysmalloc申请内存。而是在else if里面再次调用mslloc_consolidate对fastbins中的chunk进行整理,然后进入下一次循环,在后面的循环中再次判断是否有可用的chunk可以使用,如果还没有,最后就要执行sysmalloc函数进行申请内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

总结

①请求大小在fastbin的范围内:在fastbins中找是否有对应的chunk可以使用
②请求大小在smallbin的范围内:在smallbin中找是否有对应的chunk可以使用
③请求大小在largebin的范围内:先调用malloc_consolidate对fastbins进行整理。然后在unsortedbin中查看是否有满足要求的chunk可以使用
④在largebin中寻找可用的chunk来使用
⑤寻找较大的bin链中是否有可用的chunk来使用
⑥切割topchunk来使用
⑦topchunk也不够了,再次调用malloc_consolidate整理fastbins
⑧topchunk不够用,再次malloc_consolidate之后还没有可以用的,最终调用sysmalloc(系统调用)申请内存

相关文章
评论
分享
  • Alloc to Stack&Arbitary Alloc

    Alloc to Stack和Arbitary Alloc都利用了fastbin链表的特性。 Alloc To Stack利用了fastbin链表的特性。当前的chunk的fd指向下一个chunk。Alloc To Stack核心...

    Alloc to Stack&Arbitary Alloc
  • House of Spirit

    House of Spirit针对fastbin,也是fastbin attach的一种。核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。 原理House of Spi...

    House of Spirit
  • Fastbin Double Free

    double free 是任意地址写的一种技巧,指堆上的某块内存被释放后,并没有将指向该堆块的指针清零,那么,我们就可以利用程序的其他部分对该内存进行再次的free, 利用条件Fastbin Double Free 能够成功利用主...

    Fastbin Double Free
Please check the parameter of comment in config.yml of hexo-theme-Annie!