我们写过很多C程序了,经常会分配内存。记得刚学C语言时老师说过,可以向两个地方申请内存:一个是栈、一个是堆。小块内存向栈申请,函数调用结束后程序会自动释放内存。大块内存向堆申请,记得一定要自己释放,否则会造成内存泄漏。向堆申请内存直接调用malloc()就可以了,参数是你申请的内存量。释放内存时直接调用free()就可以了,参数是内存块指针。
看似平静的海面,海底则波涛汹涌。当时还没有学操作系统原理,更没有读过Linux内核代码。现在仔细想想才发现申请动态内存是一件多么麻烦的事情。动态内存管理涉及到两个层面的问题:内核层面和用户层面。系统中的内存如何管理这是内核考虑的事情,总不能让应用程序随便使用系统中的内存吧。内核向应用程序提供了接口(为此Linux提供了两个系统调用brk和mmap),当应用程序需要申请内存时向内核提出请求,内核查找并分配一块可用内存供应用程序使用。这部分内容属于内核范畴,不属于C基础库,因此不深入说了。那么用户层面做什么呢?用户层面需要合理管理内存申请和释放请求。比如:brk()可以扩充或收缩堆的大小,你总不能每分配一次内存就调用一次brk()吧?释放内存时更麻烦,你必须保证内存块的释放顺序。比如先申请了内存块a,然后申请了内存块b,然后释放a(b仍然在使用),如果释放a时调用了brk()就会出问题。你不能在使用b的同时释放a。
好在出现了一个叫做“内存分配器”的东西,内存分配器接管了应用程序申请内存和释放内存的请求,应用程序再也不需要直接调用brk()和mmap()了,而是向内存分配器提交申请。有了内存分配器,我们只需要记住malloc()和free()两个接口函数就可以了,其他繁琐事情全部交给内存分配器负责了。申请内存时,内存分配器会一次向内核申请大量内存,然后分批交给应用程序,从而提高了效率。释放内存时,应用程序也是将内存释放给内存分配器,内存分配器在合适的时候再将内存释放会内核。
dlmalloc就是一种内存分配器,由Doug Lea在1987年开发完成,这是Android系统中使用的内存分配器。而Linux系统中采用的是ptmalloc,ptmalloc在dlmalloc的基础上进行了改进,以更好适应多线程。dlmalloc采用两种方式申请内存,如果应用程序单次申请的内存量小于256kb,dlmalloc调用brk()扩展进程堆空间,但是dlmalloc向内核申请的内存量大于应用程序申请的内存量,申请到内存后dlmalloc将内存分成两块,一块返回给应用程序,另一块作为空闲内存先保留起来。下次应用程序申请内存时dlmalloc就不需要向内核申请内存了,从而加快内存分配效率。当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。
dlmalloc中,申请到的内存被分割成若干个内存块,dlmalloc采用两种不同的数据结构表示这些内存块。小内存块保存在链表中,用struct malloc_chunk表示;大内存块保存在树形结构中,用struct malloc_tree_chunk表示。struct malloc_chunk结构如下:
[cpp] view plain copy print ?
- struct malloc_chunk {
- size_t prev_foot;
- size_t head;
- struct malloc_chunk* fd;
- struct malloc_chunk* bk;
- };
fd表示链表中后面一个malloc_chunk结构,bk表示链表中前一个malloc_chunk结构。head表示这个malloc_chunk代表内存块的大小,另外还包含了一些标志信息。prev_foot表示前一个malloc_chunk的大小,这里的"前一个"不是链表中的"前一个",而是与这个malloc_chunk地址相邻的"前一个"。通过prev_foot和size两个字段dlmalloc就可以快速找到地址相邻的前一个和后一个malloc_chunk结构。
当内存块被分配给应用程序后,就会被从链表中摘除,这时malloc_chunk结构中的fd和bk两个字段就没有意义了,因此可以供应用程序使用。我们调用malloc()申请内存时,malloc()会返回一个指针,指向申请到的内存块的起始地址p,其实这个地址前还有一个malloc_chunk结构,我们可以通过p-8得到malloc_chunk结构的指针。反过来也可以通过malloc_chunk指针得到分配给应用程序的内存块的起始地址。为此dlmalloc定义了两个宏:
[cpp] view plain copy print ?
- typedef struct malloc_chunk* mchunkptr;
-
- #define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES))
- #define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))
我们看下面这个例子:
上面这块内存区域中包括两个内存块,分别为chunk1和chunk2,紧接着malloc_chunk结构的就是供应用程序使用的内存。按照前面的分析,fd和bk两个字段也可以供应用程序使用。因此ptr1 = chunk1 + 8,ptr2 = chunk2 + 8。还有一点需要注意的是,只有当前面一个chunk空闲时malloc_chunk结构中的prev_foot才保存前一个chunk的大小;当前面一个chunk分配给应用程序后,prev_foot字段也可以供应用程序使用。上图中,当chunk1分配给应用程序后,chunk2中的prev_foot字段就没有意义了,可以供应用程序使用。dlmalloc返回给应用程序的地址是ptr1,这个内存块的大小是size1 + 8 + 4。因此,malloc_chunk结构中,只有head字段永远不会挪作他用,其他三个字段都可以供应用程序使用,通过这种复用最大限度地减少了dlmalloc本身占用的内存。
dlmalloc对应用程序申请的内存长度有限制,要求内存块长度(包括malloc_chunk结构占用的内存)必须是8字节的倍数。假如应用程序调用malloc(13)申请长度为13字节的内存块,dlmalloc最终分配内存块大小是24字节,除去malloc_chunk结构中head占用的4字节,分配给应用程序的内存块大小是20字节。当然,应用程序不要揣测内存块的实际大小,虽然dlmalloc分配了20字节,但是应用程序最好只使用13字节,不要使用剩余的7字节。否则有两方面后果:(1)应用程序显得混乱,其他人可能无法读懂你的代码。(2)返回多少字节与内存分配器的实现方式有关,换另外一种内存分配器可能返回的就不是20字节了,如果应用程序使用超过13个字节就可能覆盖其他数据了,程序移植性差。
malloc_chunk结构可以表示的最小内存块是16字节,最大内存块是248字节,因此malloc_chunk可以表示16、24、32、40、......、248共30种长度的内存块。dlmalloc定义了30条链表,相同长度的空闲内存块保存在一个链表中。
超过248字节的内存就属于大块内存了,大块内存用malloc_tree_chunk表示,这个数据结构定义如下:
[cpp] view plain copy print ?
- struct malloc_tree_chunk {
-
- size_t prev_foot;
- size_t head;
- struct malloc_tree_chunk* fd;
- struct malloc_tree_chunk* bk;
-
- struct malloc_tree_chunk* child[2];
- struct malloc_tree_chunk* parent;
- bindex_t index;
- };
其中prev_foot和head的定义跟malloc_chunk中的定义完全相同。那么其他几个字段表示什么含义呢?dlmalloc中小内存块只有30种情况,可以用30条链表存储;但是大内存块有无数种情况(256、264、272、......),因此就不能用链表表示了,大内存块保存在树形结构中,dlmalloc定义了32棵树存储大内存块,每棵树中存储若干种长度的内存块,每棵树保存的内存块范围如下:
dlmalloc中根据内存块大小计算所在树的编号的宏如下:
[cpp] view plain copy print ?
- #define compute_tree_index(S, I)\
- {\
- size_t X = S >> TREEBIN_SHIFT; \
- if (X == 0)\
- I = 0;\
- else if (X > 0xFFFF)\
- I = NTREEBINS-1; \
- else {\
- unsigned int K;\
- __asm__("bsrl %1,%0\n\t" : "=r" (K) : "rm" (X));\
- I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
- }\
- }
如果感兴趣可以采用这个宏计算一下。我们看一下单棵树中保存的空闲内存块,以编号为0的树为例,这棵树中内存块的范围是[256, 384),按照前面规定内存块的大小必须是8的倍数,因此这棵树中保存的内存块长度分别为256, 264, 272, 280, 288, 296, 304, 312, 320, 328, 336, 344, 352, 360, 368, 376,共16种长度,每种长度的内存块作为树中一个节点。这棵树中可能保存了多个相同长度的内存块,这些相同长度的内存块构成了一棵链表,如下图所示:
现在回过头来看malloc_tree_chunk中各个字段的含义。
prev_foot表示前一个内存块的大小
head表示本内存块的大小
child表示两个子节点
parent表示父节点
index表示内存块所在树的索引号
fd表示链表中下一个内存块
bk表示链表中前面一个内存块
同样,这个结构中只有head字段保持不变,其他字段都可以供应用程序使用。
现在我们来看一个全局变量_gm_,这是struct malloc_state类型的变量,这个数据结构定义如下:
[cpp] view plain copy print ?
- struct malloc_state {
- binmap_t smallmap;
- mchunkptr smallbins[(NSMALLBINS+1)*2];
-
- binmap_t treemap;
- tbinptr treebins[NTREEBINS];
-
- mchunkptr dv;
- size_t dvsize;
-
- mchunkptr top;
- size_t topsize;
-
- char* least_addr;
- size_t trim_check;
-
- size_t magic;
- size_t footprint;
- #if USE_MAX_ALLOWED_FOOTPRINT
- size_t max_allowed_footprint;
- #endif
- size_t max_footprint;
- flag_t mflags;
- #if USE_LOCKS
- MLOCK_T mutex;
- #endif /* USE_LOCKS */
- msegment seg;
- };
-
- static struct malloc_state _gm_;
我们重点关注前8个字段。smallbins就是dlmalloc中定义的30条链表(加上长度为0和8的内存块,共32条链表)。smalbins[0]-smallbins[3]共16字节,表示一个malloc_chunk结构,对应长度为0的链表。smalbins[2]-smallbins[5]共16字节,表示一个malloc_chunk结构,对应长度为8的链表,以此类推。可以看到相邻两个malloc_chunk结构有重合,这是因为作为链表使用时,malloc_chunk结构中的prev_foot和head字段没有意义,因此可以重合使用。smallmap是smallbins的位图,某个比特置位表示对应的链表上有空闲内存块,比特清零表示对应的链表为空。treebins表示dlmalloc中32棵树,treemap是treebins的位图,置位表示对应树中有空闲内存块,清零表示对应树为空。dv是一个特殊的内存块,如果dlmalloc中找不到一个合适大小的内存块分配给应用程序,那么dlmalloc会将一个较大的内存块分割成两个较小的内存块,一块给应用程序使用,另外一块保存在dv中。下载再找不到合适大小的内存块时,如果dv大小大于应用程序请求的内存块,dlmalloc会将dv分割成两块,一块给应用程序,另一块仍保存在dv中;如果dv小于应用程序请求的内存块,dlmalloc首先将dv保存在链表或树中,然后挑选另外一个内存块分割,一块给应用程序,另一块保存在dv中。因此dlmalloc分配内存块的原则是先匹配大小,后匹配位置,尽量挑选合适大小的内存块给应用程序,实在找不到合适的内存块时就尽量从同一个位置分割内存块,以提高效率(程序执行的局部性原理)。dvsize就是dv表示内存块的大小。top是另外一个特殊的内存块,表示堆空间中对顶端的内存块。dlmalloc尽量不使用这个内存块,只有在_gm_中没有合适大小的内存块并且没有更大的内存块可供分割时才使用top中的内存。为什么尽量不要使用top呢?因为当top被占用时dlmalloc没办法释放其他空闲内存,dlmalloc收缩堆时必须从高地址向低地址收缩,所以主要高地址的内存被占用,即使堆中有再多的空闲内存也没办法释放。topsize表示top的大小。
[cpp] view plain copy print ?
- void* dlmalloc(size_t bytes) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- if (!PREACTION(gm)) {
- void* mem;
- size_t nb;
-
- if (bytes <= MAX_SMALL_REQUEST) {
- bindex_t idx;
- binmap_t smallbits;
-
- nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);
-
- idx = small_index(nb);
-
-
- smallbits = gm->smallmap >> idx;
- if ((smallbits & 0x3U) != 0) {
- mchunkptr b, p;
-
- idx += ~smallbits & 1;
- b = smallbin_at(gm, idx);
- p = b->fd;
-
- assert(chunksize(p) == small_index2size(idx));
- unlink_first_small_chunk(gm, b, p, idx);
-
- 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) {
-
- if (smallbits != 0) {
-
- 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);
-
- p = b->fd;
- assert(chunksize(p) == small_index2size(i));
- unlink_first_small_chunk(gm, b, p, i);
- rsize = small_index2size(i) - nb;
-
- if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)
- set_inuse_and_pinuse(gm, p, small_index2size(i));
- else {
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- r = chunk_plus_offset(p, nb);
- set_size_and_pinuse_of_free_chunk(r, rsize);
- replace_dv(gm, r, rsize);
- }
- mem = chunk2mem(p);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
-
-
- else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
- }
- }
-
- else if (bytes >= MAX_REQUEST)
- nb = MAX_SIZE_T;
- else {
- nb = pad_request(bytes);
-
- if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
- }
-
-
- if (nb <= gm->dvsize) {
- size_t rsize = gm->dvsize - nb;
- mchunkptr p = gm->dv;
- if (rsize >= MIN_CHUNK_SIZE) {
- mchunkptr r = gm->dv = chunk_plus_offset(p, nb);
- gm->dvsize = rsize;
-
- set_size_and_pinuse_of_free_chunk(r, rsize);
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- }
- else {
- size_t dvs = gm->dvsize;
- gm->dvsize = 0;
- gm->dv = 0;
- set_inuse_and_pinuse(gm, p, dvs);
- }
- mem = chunk2mem(p);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
- else if (nb < gm->topsize) {
- size_t rsize = gm->topsize -= nb;
- mchunkptr p = gm->top;
- mchunkptr r = gm->top = chunk_plus_offset(p, nb);
- r->head = rsize | PINUSE_BIT;
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- mem = chunk2mem(p);
- check_top_chunk(gm, gm->top);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
- mem = sys_alloc(gm, nb);
-
- postaction:
- POSTACTION(gm);
- return mem;
- }
-
- return 0;
- }
这个分配过程还是很麻烦的,因为涉及到多种情况。分析代码流程时记住一个分配顺序就可以了:首选大小合适的内存块,其次分割dv(只有申请的内存量不超过248字节(包括malloc_chunk占用的内存)时才能使用dv),再其次分割一个大的内存块,再其次使用top chunk,最后向内核申请内存。现在分析代码,dlmalloc()首先根据申请的内存量区分了两种情况,因为small bins中内存块的最大长度是248,因此当应用程序请求的内存量不超过AX_SMALL_REQUEST(244字节,因为malloc_chunk结构要占用4字节)时可以从small bins中分配内存;如果超过了244字节那么就需要从tree bins中分配内存。
先看不超过244字节的情况。dlmalloc首先调整了申请的内存量nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);。pad_request()按照两个因素进行了调整,首先增加malloc_chunk结构占用的4字节,然后将长度按照8字节对齐,因此nb才是dlmalloc需要分配的内存块的大小。然后根据nb计算内存块所在的链表。dlmalloc按照如下顺序分配内存块:
(1)从对应的链表或相邻链表分配
从nb对应的链表中分配内存块是最理想的情况,这种情况下不需要对内存块进行任何操作,直接从链表中取一个内存块给应用程序使用就可以了。如果对应链表为空,可以考虑从相邻链表中分配内存块,相邻链表中内存块长度比对应链表大8个字节,但是dlmalloc中内存块的最小长度是16字节,因此多出来的8字节不能作为一个单独的内存块。这种情况下就没有必要对内存块进行分割了,直接将内存块给应用程序使用就可以了。
(2)从dv分配
如果nb小于dv中内存块大小,那么就将dv分割成两块,一块给应用程序使用,另一块继续作为dv。
(3)从其他链表分配
这种情况下dlmalloc将一个大的内存块分割成两块,一块给应用程序使用,另一块保存在dv中,而dv中原先的内存块保存在对应的链表中。由于内存块大于nb的链表不止一条,那么分割哪条链表中的内存块呢?dlmalloc挑选的是不为空且内存块长度与nb最接近的链表。
(4)从tree bins分配
如果前面三种情况均不能分配到内存,那么dlmalloc就使用tree bins中的内存块。由于tree bins中所有内存块长度都大于nb,因此dlmalloc从tree bins中挑选最小的内存块分割,然后将这个内存块分割成两块,一块给应用程序使用,另一块保存在dv中,而dv中原先的内存块保存在对应的链表中。这种情况是在函数tmalloc_small()中完成的。
(5)从top chunk分配
如果nb小于top chunk中的内存大小,dlmalloc就将top chunk分割成两块,一块给应用程序使用,另一块继续作为top chunk。
(6)向内核申请内存
这是最后一种情况。程序执行到这里说明dlmalloc中没有合适的内存块,只能向内核申请内存了。这是通过sys_alloc()完成的。
现在看超过244字节的情况,这种情况下也需要首先调整内存块大小。由于调整后的长度大于248字节,因此不可能从small bins中找到合适的内存块,并且dlmalloc规定不能使用dv。包含三种情况:
(1)从tree bins中分配内存
如果tree bins中正好包含长度是nb的内存块,那么直接给应用程序使用就行了。如果没有长度是nb的内存块,那么就需要将一块更大的内存块分割成两块,一块给应用程序使用,另一块保存在small bins中(如果长度不超过248字节)或tree bins中(长度超过248字节)。这是在函数tmalloc_large()中实现的。
(2)从top chunk分配内存
(3)向内核申请内存
这里就不进一步讲解tmalloc_small()和tmalloc_large()了,因为这两个函数原理很简单,就是从一棵树中挑选一个合适的内存块,然后分割成两块,一块给应用程序使用,另一块继续保存在dlmalloc中。下面详细分析dlmalloc向内核申请内存的过程。向内核申请内存时首先要考虑的问题是向内核申请多少内存?如果只满足本次需求,那么很可能应用程序下次调用malloc()时dlmalloc还需要向内核申请内存。由于系统调用效率比较低,因此比较好的办法是dlmalloc向内核多申请一些内存,这样下次就不必再向内核申请了。看下面一个数据结构:
[cpp] view plain copy print ?
- struct malloc_params {
- size_t magic;
- size_t page_size;
- size_t granularity;
- size_t mmap_threshold;
- size_t trim_threshold;
- flag_t default_mflags;
- };
这是dlmalloc向内核申请内存时使用的一个数据结构,我们注释了数据结构中各个字段的含义,因此dlmalloc每次至少向内核申请4kb内存。
[cpp] view plain copy print ?
- static void* sys_alloc(mstate m, size_t nb) {
- char* tbase = CMFAIL;
- size_t tsize = 0;
- flag_t mmap_flag = 0;
-
- init_mparams();
-
-
-
- if (use_mmap(m) && nb >= mparams.mmap_threshold) {
- void* mem = mmap_alloc(m, nb);
- if (mem != 0)
-
- return mem;
- }
-
- #if USE_MAX_ALLOWED_FOOTPRINT // 这个宏是0,跳过下面这段代码.
-
-
-
- {
- size_t new_footprint = m->footprint +
- granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
- if (new_footprint <= m->footprint ||
- new_footprint > m->max_allowed_footprint)
- return 0;
- }
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT)
- #define is_extern_segment(S) ((S)->sflags & EXTERN_BIT)
-
-
- if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {
- char* br = CMFAIL;
-
- msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);
- size_t asize = 0;
- ACQUIRE_MORECORE_LOCK();
-
-
-
-
- if (ss == 0) {
-
- char* base = (char*)CALL_MORECORE(0);
- if (base != CMFAIL) {
-
-
- asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
-
- if (!is_page_aligned(base)) {
- asize += (page_align((size_t)base) - (size_t)base);
- #if USE_MAX_ALLOWED_FOOTPRINT
-
-
-
- {
- size_t new_footprint = m->footprint + asize;
- if (new_footprint <= m->footprint ||
- new_footprint > m->max_allowed_footprint) {
- asize = HALF_MAX_SIZE_T;
- }
- }
- #endif
- }
-
-
-
- if (asize < HALF_MAX_SIZE_T &&
-
- (br = (char*)(CALL_MORECORE(asize))) == base) {
- tbase = base;
- tsize = asize;
- }
- }
- }
-
- else {
-
- asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE);
-
-
- if (asize < HALF_MAX_SIZE_T &&
- (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {
- tbase = br;
- tsize = asize;
- }
- }
-
-
-
- if (tbase == CMFAIL) {
- if (br != CMFAIL) {
- if (asize < HALF_MAX_SIZE_T &&
- asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) {
- size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize);
- if (esize < HALF_MAX_SIZE_T) {
- char* end = (char*)CALL_MORECORE(esize);
- if (end != CMFAIL)
- asize += esize;
- else {
- CALL_MORECORE(-asize);
- br = CMFAIL;
- }
- }
- }
- }
- if (br != CMFAIL) {
- tbase = br;
- tsize = asize;
- }
- else
- disable_contiguous(m);
- }
-
- RELEASE_MORECORE_LOCK();
- }
-
-
-
-
- if (HAVE_MMAP && tbase == CMFAIL) {
- size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;
- size_t rsize = granularity_align(req);
- if (rsize > nb) {
- char* mp = (char*)(CALL_MMAP(rsize));
- if (mp != CMFAIL) {
- tbase = mp;
- tsize = rsize;
- mmap_flag = IS_MMAPPED_BIT;
- }
- }
- }
-
-
-
- if (HAVE_MORECORE && tbase == CMFAIL) {
- size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
- if (asize < HALF_MAX_SIZE_T) {
- char* br = CMFAIL;
- char* end = CMFAIL;
- ACQUIRE_MORECORE_LOCK();
- br = (char*)(CALL_MORECORE(asize));
- end = (char*)(CALL_MORECORE(0));
- RELEASE_MORECORE_LOCK();
- if (br != CMFAIL && end != CMFAIL && br < end) {
- size_t ssize = end - br;
- if (ssize > nb + TOP_FOOT_SIZE) {
- tbase = br;
- tsize = ssize;
- }
- }
- }
- }
-
-
-
- if (tbase != CMFAIL) {
-
- if ((m->footprint += tsize) > m->max_footprint)
- m->max_footprint = m->footprint;
-
-
-
- if (!is_initialized(m)) {
- m->seg.base = m->least_addr = tbase;
- m->seg.size = tsize;
- m->seg.sflags = mmap_flag;
- m->magic = mparams.magic;
- init_bins(m);
- if (is_global(m))
- init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);
- else {
-
- mchunkptr mn = next_chunk(mem2chunk(m));
- init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);
- }
- }
-
- else {
-
- msegmentptr sp = &m->seg;
- while (sp != 0 && tbase != sp->base + sp->size)
- sp = sp->next;
-
- if (sp != 0 &&
- !is_extern_segment(sp) &&
- (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&
- segment_holds(sp, m->top)) {
- sp->size += tsize;
- init_top(m, m->top, m->topsize + tsize);
- }
- else {
- if (tbase < m->least_addr)
- m->least_addr = tbase;
- sp = &m->seg;
- while (sp != 0 && sp->base != tbase + tsize)
- sp = sp->next;
- if (sp != 0 &&
- !is_extern_segment(sp) &&
- (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {
- char* oldbase = sp->base;
- sp->base = tbase;
- sp->size += tsize;
- return prepend_alloc(m, tbase, oldbase, nb);
- }
- else
- add_segment(m, tbase, tsize, mmap_flag);
- }
-
- }
-
-
- if (nb < m->topsize) {
- size_t rsize = m->topsize -= nb;
- mchunkptr p = m->top;
- mchunkptr r = m->top = chunk_plus_offset(p, nb);
- r->head = rsize | PINUSE_BIT;
- set_size_and_pinuse_of_inuse_chunk(m, p, nb);
- check_top_chunk(m, m->top);
- check_malloced_chunk(m, chunk2mem(p), nb);
- return chunk2mem(p);
- }
- }
-
-
- MALLOC_FAILURE_ACTION;
- return 0;
- }
这个函数也相当复杂,因为dlmalloc适用于各种操作系统,每种系统申请内存的方式不一定相同。Linux系统包含两种方式:(1)brk()扩展堆;(2)mmap()映射一块新的内存区。malloc_params结构中的mmap_threshold是一个阈值,默认值是256kb,当申请的内存量超过这个阈值时dlmalloc首先mmap()方式映射一块单独的内存区域,如果mmap()失败了dlmalloc尝试brk()方式扩展堆。如果申请的内存量没有超过这个阈值dlmalloc首先brk()方式,如果brk()失败了dlmalloc再尝试mmap()方式。
[cpp] view plain copy print ?
- void* dlmalloc(size_t bytes) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- if (!PREACTION(gm)) {
- void* mem;
- size_t nb;
-
- if (bytes <= MAX_SMALL_REQUEST) {
- bindex_t idx;
- binmap_t smallbits;
-
- nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);
-
- idx = small_index(nb);
-
-
- smallbits = gm->smallmap >> idx;
- if ((smallbits & 0x3U) != 0) {
- mchunkptr b, p;
-
- idx += ~smallbits & 1;
- b = smallbin_at(gm, idx);
- p = b->fd;
-
- assert(chunksize(p) == small_index2size(idx));
- unlink_first_small_chunk(gm, b, p, idx);
-
- 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) {
-
- if (smallbits != 0) {
-
- 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);
-
- p = b->fd;
- assert(chunksize(p) == small_index2size(i));
- unlink_first_small_chunk(gm, b, p, i);
- rsize = small_index2size(i) - nb;
-
- if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)
- set_inuse_and_pinuse(gm, p, small_index2size(i));
- else {
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- r = chunk_plus_offset(p, nb);
- set_size_and_pinuse_of_free_chunk(r, rsize);
- replace_dv(gm, r, rsize);
- }
- mem = chunk2mem(p);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
-
-
- else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
- }
- }
-
- else if (bytes >= MAX_REQUEST)
- nb = MAX_SIZE_T;
- else {
- nb = pad_request(bytes);
-
- if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
- }
-
-
- if (nb <= gm->dvsize) {
- size_t rsize = gm->dvsize - nb;
- mchunkptr p = gm->dv;
- if (rsize >= MIN_CHUNK_SIZE) {
- mchunkptr r = gm->dv = chunk_plus_offset(p, nb);
- gm->dvsize = rsize;
-
- set_size_and_pinuse_of_free_chunk(r, rsize);
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- }
- else {
- size_t dvs = gm->dvsize;
- gm->dvsize = 0;
- gm->dv = 0;
- set_inuse_and_pinuse(gm, p, dvs);
- }
- mem = chunk2mem(p);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
- else if (nb < gm->topsize) {
- size_t rsize = gm->topsize -= nb;
- mchunkptr p = gm->top;
- mchunkptr r = gm->top = chunk_plus_offset(p, nb);
- r->head = rsize | PINUSE_BIT;
- set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
- mem = chunk2mem(p);
- check_top_chunk(gm, gm->top);
- check_malloced_chunk(gm, mem, nb);
- goto postaction;
- }
-
- mem = sys_alloc(gm, nb);
-
- postaction:
- POSTACTION(gm);
- return mem;
- }
-
- return 0;
- }
这个分配过程还是很麻烦的,因为涉及到多种情况。分析代码流程时记住一个分配顺序就可以了:首选大小合适的内存块,其次分割dv(只有申请的内存量不超过248字节(包括malloc_chunk占用的内存)时才能使用dv),再其次分割一个大的内存块,再其次使用top chunk,最后向内核申请内存。现在分析代码,dlmalloc()首先根据申请的内存量区分了两种情况,因为small bins中内存块的最大长度是248,因此当应用程序请求的内存量不超过AX_SMALL_REQUEST(244字节,因为malloc_chunk结构要占用4字节)时可以从small bins中分配内存;如果超过了244字节那么就需要从tree bins中分配内存。
先看不超过244字节的情况。dlmalloc首先调整了申请的内存量nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);。pad_request()按照两个因素进行了调整,首先增加malloc_chunk结构占用的4字节,然后将长度按照8字节对齐,因此nb才是dlmalloc需要分配的内存块的大小。然后根据nb计算内存块所在的链表。dlmalloc按照如下顺序分配内存块:
(1)从对应的链表或相邻链表分配
从nb对应的链表中分配内存块是最理想的情况,这种情况下不需要对内存块进行任何操作,直接从链表中取一个内存块给应用程序使用就可以了。如果对应链表为空,可以考虑从相邻链表中分配内存块,相邻链表中内存块长度比对应链表大8个字节,但是dlmalloc中内存块的最小长度是16字节,因此多出来的8字节不能作为一个单独的内存块。这种情况下就没有必要对内存块进行分割了,直接将内存块给应用程序使用就可以了。
(2)从dv分配
如果nb小于dv中内存块大小,那么就将dv分割成两块,一块给应用程序使用,另一块继续作为dv。
(3)从其他链表分配
这种情况下dlmalloc将一个大的内存块分割成两块,一块给应用程序使用,另一块保存在dv中,而dv中原先的内存块保存在对应的链表中。由于内存块大于nb的链表不止一条,那么分割哪条链表中的内存块呢?dlmalloc挑选的是不为空且内存块长度与nb最接近的链表。
(4)从tree bins分配
如果前面三种情况均不能分配到内存,那么dlmalloc就使用tree bins中的内存块。由于tree bins中所有内存块长度都大于nb,因此dlmalloc从tree bins中挑选最小的内存块分割,然后将这个内存块分割成两块,一块给应用程序使用,另一块保存在dv中,而dv中原先的内存块保存在对应的链表中。这种情况是在函数tmalloc_small()中完成的。
(5)从top chunk分配
如果nb小于top chunk中的内存大小,dlmalloc就将top chunk分割成两块,一块给应用程序使用,另一块继续作为top chunk。
(6)向内核申请内存
这是最后一种情况。程序执行到这里说明dlmalloc中没有合适的内存块,只能向内核申请内存了。这是通过sys_alloc()完成的。
现在看超过244字节的情况,这种情况下也需要首先调整内存块大小。由于调整后的长度大于248字节,因此不可能从small bins中找到合适的内存块,并且dlmalloc规定不能使用dv。包含三种情况:
(1)从tree bins中分配内存
如果tree bins中正好包含长度是nb的内存块,那么直接给应用程序使用就行了。如果没有长度是nb的内存块,那么就需要将一块更大的内存块分割成两块,一块给应用程序使用,另一块保存在small bins中(如果长度不超过248字节)或tree bins中(长度超过248字节)。这是在函数tmalloc_large()中实现的。
(2)从top chunk分配内存
(3)向内核申请内存
这里就不进一步讲解tmalloc_small()和tmalloc_large()了,因为这两个函数原理很简单,就是从一棵树中挑选一个合适的内存块,然后分割成两块,一块给应用程序使用,另一块继续保存在dlmalloc中。下面详细分析dlmalloc向内核申请内存的过程。向内核申请内存时首先要考虑的问题是向内核申请多少内存?如果只满足本次需求,那么很可能应用程序下次调用malloc()时dlmalloc还需要向内核申请内存。由于系统调用效率比较低,因此比较好的办法是dlmalloc向内核多申请一些内存,这样下次就不必再向内核申请了。看下面一个数据结构:
[cpp] view plain copy print ?
- struct malloc_params {
- size_t magic;
- size_t page_size;
- size_t granularity;
- size_t mmap_threshold;
- size_t trim_threshold;
- flag_t default_mflags;
- };
这是dlmalloc向内核申请内存时使用的一个数据结构,我们注释了数据结构中各个字段的含义,因此dlmalloc每次至少向内核申请4kb内存。
[cpp] view plain copy print ?
- static void* sys_alloc(mstate m, size_t nb) {
- char* tbase = CMFAIL;
- size_t tsize = 0;
- flag_t mmap_flag = 0;
-
- init_mparams();
-
-
-
- if (use_mmap(m) && nb >= mparams.mmap_threshold) {
- void* mem = mmap_alloc(m, nb);
- if (mem != 0)
-
- return mem;
- }
-
- #if USE_MAX_ALLOWED_FOOTPRINT // 这个宏是0,跳过下面这段代码.
-
-
-
- {
- size_t new_footprint = m->footprint +
- granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
- if (new_footprint <= m->footprint ||
- new_footprint > m->max_allowed_footprint)
- return 0;
- }
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT)
- #define is_extern_segment(S) ((S)->sflags & EXTERN_BIT)
-
-
- if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {
- char* br = CMFAIL;
-
- msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);
- size_t asize = 0;
- ACQUIRE_MORECORE_LOCK();
-
-
-
-
- if (ss == 0) {
-
- char* base = (char*)CALL_MORECORE(0);
- if (base != CMFAIL) {
-
-
- asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
-
- if (!is_page_aligned(base)) {
- asize += (page_align((size_t)base) - (size_t)base);
- #if USE_MAX_ALLOWED_FOOTPRINT
-
-
-
- {
- size_t new_footprint = m->footprint + asize;
- if (new_footprint <= m->footprint ||
- new_footprint > m->max_allowed_footprint) {
- asize = HALF_MAX_SIZE_T;
- }
- }
- #endif
- }
-
-
-
- if (asize < HALF_MAX_SIZE_T &&
-
- (br = (char*)(CALL_MORECORE(asize))) == base) {
- tbase = base;
- tsize = asize;
- }
- }
- }
-
- else {
-
- asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE);
-
-
- if (asize < HALF_MAX_SIZE_T &&
- (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {
- tbase = br;
- tsize = asize;
- }
- }
-
-
-
- if (tbase == CMFAIL) {
- if (br != CMFAIL) {
- if (asize < HALF_MAX_SIZE_T &&
- asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) {
- size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize);
- if (esize < HALF_MAX_SIZE_T) {
- char* end = (char*)CALL_MORECORE(esize);
- if (end != CMFAIL)
- asize += esize;
- else {
- CALL_MORECORE(-asize);
- br = CMFAIL;
- }
- }
- }
- }
- if (br != CMFAIL) {
- tbase = br;
- tsize = asize;
- }
- else
- disable_contiguous(m);
- }
-
- RELEASE_MORECORE_LOCK();
- }
-
-
-
-
- if (HAVE_MMAP && tbase == CMFAIL) {
- size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;
- size_t rsize = granularity_align(req);
- if (rsize > nb) {
- char* mp = (char*)(CALL_MMAP(rsize));
- if (mp != CMFAIL) {
- tbase = mp;
- tsize = rsize;
- mmap_flag = IS_MMAPPED_BIT;
- }
- }
- }
-
-
-
- if (HAVE_MORECORE && tbase == CMFAIL) {
- size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);
- if (asize < HALF_MAX_SIZE_T) {
- char* br = CMFAIL;
- char* end = CMFAIL;
- ACQUIRE_MORECORE_LOCK();
- br = (char*)(CALL_MORECORE(asize));
- end = (char*)(CALL_MORECORE(0));
- RELEASE_MORECORE_LOCK();
- if (br != CMFAIL && end != CMFAIL && br < end) {
- size_t ssize = end - br;
- if (ssize > nb + TOP_FOOT_SIZE) {
- tbase = br;
- tsize = ssize;
- }
- }
- }
- }
-
-
-
- if (tbase != CMFAIL) {
-
- if ((m->footprint += tsize) > m->max_footprint)
- m->max_footprint = m->footprint;
-
-
-
- if (!is_initialized(m)) {
- m->seg.base = m->least_addr = tbase;
- m->seg.size = tsize;
- m->seg.sflags = mmap_flag;
- m->magic = mparams.magic;
- init_bins(m);
- if (is_global(m))
- init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);
- else {
-
- mchunkptr mn = next_chunk(mem2chunk(m));
- init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);
- }
- }
-
- else {
-
- msegmentptr sp = &m->seg;
- while (sp != 0 && tbase != sp->base + sp->size)
- sp = sp->next;
-
- if (sp != 0 &&
- !is_extern_segment(sp) &&
- (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&
- segment_holds(sp, m->top)) {
- sp->size += tsize;
- init_top(m, m->top, m->topsize + tsize);
- }
- else {
- if (tbase < m->least_addr)
- m->least_addr = tbase;
- sp = &m->seg;
- while (sp != 0 && sp->base != tbase + tsize)
- sp = sp->next;
- if (sp != 0 &&
- !is_extern_segment(sp) &&
- (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {
- char* oldbase = sp->base;
- sp->base = tbase;
- sp->size += tsize;
- return prepend_alloc(m, tbase, oldbase, nb);
- }
- else
- add_segment(m, tbase, tsize, mmap_flag);
- }
-
- }
-
-
- if (nb < m->topsize) {
- size_t rsize = m->topsize -= nb;
- mchunkptr p = m->top;
- mchunkptr r = m->top = chunk_plus_offset(p, nb);
- r->head = rsize | PINUSE_BIT;
- set_size_and_pinuse_of_inuse_chunk(m, p, nb);
- check_top_chunk(m, m->top);
- check_malloced_chunk(m, chunk2mem(p), nb);
- return chunk2mem(p);
- }
- }
-
-
- MALLOC_FAILURE_ACTION;
- return 0;
- }
这个函数也相当复杂,因为dlmalloc适用于各种操作系统,每种系统申请内存的方式不一定相同。Linux系统包含两种方式:(1)brk()扩展堆;(2)mmap()映射一块新的内存区。malloc_params结构中的mmap_threshold是一个阈值,默认值是256kb,当申请的内存量超过这个阈值时dlmalloc首先mmap()方式映射一块单独的内存区域,如果mmap()失败了dlmalloc尝试brk()方式扩展堆。如果申请的内存量没有超过这个阈值dlmalloc首先brk()方式,如果brk()失败了dlmalloc再尝试mmap()方式。
这篇文章我们来讲讲释放内存的过程,也就是free()的代码流程。对于应用程序来说释放内存很简单,直接调用free(ptr)就可以了,参数是要释放的内存块指针。那么,释放内存时dlmalloc做了哪些工作呢?
[cpp] view plain copy print ?
-
-
- void dlfree(void* mem) {
-
-
-
-
-
-
- if (mem != 0) {
- mchunkptr p = mem2chunk(mem);
- #if FOOTERS // 将这个宏看作是0就可以了
- mstate fm = get_mstate_for(p);
- if (!ok_magic(fm)) {
- USAGE_ERROR_ACTION(fm, p);
- return;
- }
- #else /* FOOTERS */
- #define fm gm // 全局变量_gm_的地址
- #endif /* FOOTERS */
- if (!PREACTION(fm)) {
- check_inuse_chunk(fm, p);
- if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) {
- size_t psize = chunksize(p);
- mchunkptr next = chunk_plus_offset(p, psize);
-
- if (!pinuse(p)) {
- size_t prevsize = p->prev_foot;
-
- if ((prevsize & IS_MMAPPED_BIT) != 0) {
- prevsize &= ~IS_MMAPPED_BIT;
- psize += prevsize + MMAP_FOOT_PAD;
- if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)
- fm->footprint -= psize;
- goto postaction;
- }
- else {
- mchunkptr prev = chunk_minus_offset(p, prevsize);
- psize += prevsize;
- p = prev;
- if (RTCHECK(ok_address(fm, prev))) {
- if (p != fm->dv) {
- unlink_chunk(fm, p, prevsize);
- }
-
- else if ((next->head & INUSE_BITS) == INUSE_BITS) {
- fm->dvsize = psize;
- set_free_with_pinuse(p, psize, next);
- goto postaction;
- }
-
- }
- else
- goto erroraction;
- }
- }
-
-
- if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {
- if (!cinuse(next)) {
- if (next == fm->top) {
- size_t tsize = fm->topsize += psize;
- fm->top = p;
- p->head = tsize | PINUSE_BIT;
- if (p == fm->dv) {
- fm->dv = 0;
- fm->dvsize = 0;
- }
-
- if (should_trim(fm, tsize))
- sys_trim(fm, 0);
- goto postaction;
- }
- else if (next == fm->dv) {
- size_t dsize = fm->dvsize += psize;
- fm->dv = p;
- set_size_and_pinuse_of_free_chunk(p, dsize);
- goto postaction;
- }
- else {
- size_t nsize = chunksize(next);
- psize += nsize;
- unlink_chunk(fm, next, nsize);
- set_size_and_pinuse_of_free_chunk(p, psize);
- if (p == fm->dv) {
- fm->dvsize = psize;
- goto postaction;
- }
- }
- }
- else
- set_free_with_pinuse(p, psize, next);
-
- insert_chunk(fm, p, psize);
- check_free_chunk(fm, p);
- goto postaction;
- }
- }
- erroraction:
- USAGE_ERROR_ACTION(fm, p);
- postaction:
- POSTACTION(fm);
- }
- }
- #if !FOOTERS
- #undef fm
- #endif /* FOOTERS */
- }
又是很长一大段代码。这段代码首先将内存块标记为空闲,然后根据内存申请方式分别处理。如果内存块大于256kb,那么马上通过munmap()释放内存。如果内存块小于256kb,那么检查相邻的两个内存块是否空闲,如果空闲就跟相邻的内存块合并。然后还需要检查top chunk是否大于2mb。如果top chunk大于2mb,将top chunk释放回内核。
内存块大于256kb时释放内存的代码如下:
[cpp] view plain copy print ?
- size_t prevsize = p->prev_foot;
- if ((prevsize & IS_MMAPPED_BIT) != 0) {
- prevsize &= ~IS_MMAPPED_BIT;
- psize += prevsize + MMAP_FOOT_PAD;
- if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)
- fm->footprint -= psize;
- goto postaction;
- }
p->prev_foot包含了两项信息:前一个内存块的长度和前一个内存块的创建方式(mmap还是brk)。当申请的内存块大于256kb时dlmalloc通过mmap()申请内存,并为这个内存块创建了一个malloc_chunk结构。由于只有一个malloc_chunk结构,没有相邻的malloc_chunk结构,因此malloc_chunk中的prev_foot字段就没有意义了。这时dlmalloc将prev_foot中比特0用作标志位IS_MMAPPED_BIT,表示这个内存块是通过mmap()方式创建的。因此,如果prev_foot中的IS_MMAPPED_BIT置位了,那么就调用munmap()释放内存(CALL_MUNMAP)。
最后来看看dlmalloc收缩top chunk的代码,这是在函数sys_trim()中实现的,代码如下:
[cpp] view plain copy print ?
- static int sys_trim(mstate m, size_t pad) {
- size_t released = 0;
- if (pad < MAX_REQUEST && is_initialized(m)) {
- pad += TOP_FOOT_SIZE;
-
-
- if (m->topsize > pad) {
-
- size_t unit = mparams.granularity;
-
- size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit -
- SIZE_T_ONE) * unit;
-
- msegmentptr sp = segment_holding(m, (char*)m->top);
-
- if (!is_extern_segment(sp)) {
-
- if (is_mmapped_segment(sp)) {
- if (HAVE_MMAP &&
- sp->size >= extra &&
- !has_segment_link(m, sp)) {
- size_t newsize = sp->size - extra;
- if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||
- (CALL_MUNMAP(sp->base + newsize, extra) == 0)) {
- released = extra;
- }
- }
- }
-
- else if (HAVE_MORECORE) {
- if (extra >= HALF_MAX_SIZE_T)
- extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit;
- ACQUIRE_MORECORE_LOCK();
- {
-
- char* old_br = (char*)(CALL_MORECORE(0));
- if (old_br == sp->base + sp->size) {
-
- char* rel_br = (char*)(CALL_MORECORE(-extra));
- char* new_br = (char*)(CALL_MORECORE(0));
- if (rel_br != CMFAIL && new_br < old_br)
- released = old_br - new_br;
- }
- }
- RELEASE_MORECORE_LOCK();
- }
- }
-
- if (released != 0) {
- sp->size -= released;
- m->footprint -= released;
- init_top(m, m->top, m->topsize - released);
- check_top_chunk(m, m->top);
- }
- }
-
-
- if (HAVE_MMAP)
- released += release_unused_segments(m);
-
-
- if (released == 0)
- m->trim_check = MAX_SIZE_T;
- }
-
- return (released != 0)? 1 : 0;
- }
当申请的内存量小于256kb时,dlmalloc首先通过brk()方式扩展堆,如果失败了会尝试通过mmap()方式申请内存。因此,top chunk可能是通过brk()方式申请的,也可能是通过mmap()方式申请的。如果通过brk()方式申请的,那么就需要通过brk()收缩堆;如果通过mmap()方式申请的,那么就需要通过munmap()或mremap()释放内存。