dlmalloc(四)
来源:互联网 发布:t splines mac 编辑:程序博客网 时间:2024/05/16 23:57
这篇文章我们来讲讲释放内存的过程,也就是free()的代码流程。对于应用程序来说释放内存很简单,直接调用free(ptr)就可以了,参数是要释放的内存块指针。那么,释放内存时dlmalloc做了哪些工作呢?
// 这是释放内存的函数,调用free()后执行到这里.// 参数mem: 这是将要释放内存的指针void dlfree(void* mem) { /* Consolidate freed chunks with preceeding or succeeding bordering free chunks, if they exist, and then place in a bin. Intermixed with special cases for top, dv, mmapped chunks, and usage errors. */ // 如果是空指针,那么就不需要处理了. if (mem != 0) { mchunkptr p = mem2chunk(mem);// 首先找到内存块的起始地址 p = mem - 8.#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);// 检查这个chunk是否在使用中,这是一个检查函数. 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) { // 如果是通过mmap方式创建的内存块 prevsize &= ~IS_MMAPPED_BIT; psize += prevsize + MMAP_FOOT_PAD; if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) fm->footprint -= psize; goto postaction; } else { // 不是通过mmap方式创建的. mchunkptr prev = chunk_minus_offset(p, prevsize);// 取出前面一个chunk的结构. psize += prevsize;// 这是两个内存块的总长度 p = prev;// 这是内存块的起始地址 if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ if (p != fm->dv) {// 如果不是dv unlink_chunk(fm, p, prevsize);// 将这个内存块从malloc_state结构中删除. } // 如果是dv else if ((next->head & INUSE_BITS) == INUSE_BITS) { // 后面一个内存块在使用中,那么就处理完毕了. fm->dvsize = psize;// 修改这个chunk的长度. set_free_with_pinuse(p, psize, next); goto postaction;// 处理完毕 } // 如果后面一个内存块也是空间的,那么还需要将后面一个内存块合并到dv中. } else goto erroraction; } // end if ((prevsize & IS_MMAPPED_BIT) != 0) } // end if (!pinuse(p)) // 需要继续检查后面一个内存块是否空闲. if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { if (!cinuse(next)) { /* consolidate forward */ // 后面一个内存块也处于空闲状态,那么就可以合并了. if (next == fm->top) { // 如果后面一个chunk是top chunk,那么直接将当前合并到top chunk中就可以了. size_t tsize = fm->topsize += psize;// 这是合并后top chunk的大小 fm->top = p;// 这是合并后top chunk的起始地址 p->head = tsize | PINUSE_BIT; if (p == fm->dv) {// 同时也是dv,那么就撤销dv. fm->dv = 0; fm->dvsize = 0; } // 现在检查是否需要收缩堆空间,当top chunk大于2mb时收缩堆空间. if (should_trim(fm, tsize)) sys_trim(fm, 0);// 只有这种情况下执行到了sys_trim. goto postaction; } else if (next == fm->dv) {// 如果后面一个chunk是dv,那么直接将本内存块合并到dv中就可以了. size_t dsize = fm->dvsize += psize;// 这是合并后dv的大小 fm->dv = p;// 设置dv新的起始地址 set_size_and_pinuse_of_free_chunk(p, dsize);// 设置dv新的长度 goto postaction; } else { // 后面一个chunk是一个普通的chunk. size_t nsize = chunksize(next); psize += nsize; unlink_chunk(fm, next, nsize);// 先将后面的chunk从malloc_state中摘除. set_size_and_pinuse_of_free_chunk(p, psize); if (p == fm->dv) { fm->dvsize = psize; goto postaction; } } } // end if (!cinuse(next)) else // 后面一个chunk在使用中 set_free_with_pinuse(p, psize, next);// 修改一些标志信息 insert_chunk(fm, p, psize);// 将合并后内存块的大小将内存块添加到small bins或者tree bins中. check_free_chunk(fm, p); goto postaction; } // end if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) } 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时释放内存的代码如下:
size_t prevsize = p->prev_foot; // 前面一个内存块的大小 if ((prevsize & IS_MMAPPED_BIT) != 0) { // 如果是通过mmap方式创建的内存块 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()中实现的,代码如下:
static int sys_trim(mstate m, size_t pad) { size_t released = 0; if (pad < MAX_REQUEST && is_initialized(m)) { pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ // 调整pad,pad表示需要保留的内存量. if (m->topsize > pad) { /* Shrink top space in granularity-size units, keeping at least one */ size_t unit = mparams.granularity;// 申请/释放内存需要是这个值的倍数. // 这是需要释放的内存量. size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - SIZE_T_ONE) * unit; // 取出包含top chunk的segment. msegmentptr sp = segment_holding(m, (char*)m->top); if (!is_extern_segment(sp)) {// 这个segment是通过mmap方式创建的,那么就通过munmap()或者mremap()方式释放内存. if (is_mmapped_segment(sp)) { if (HAVE_MMAP && sp->size >= extra &&// extra是将要释放的内存量 !has_segment_link(m, sp)) { /* can't shrink if pinned */ 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; } } } // 这个segment是通过brk方式创建的,那么就通过brk()调整堆的结束位置. else if (HAVE_MORECORE) { if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; ACQUIRE_MORECORE_LOCK(); { /* Make sure end of memory is where we last set it. */ char* old_br = (char*)(CALL_MORECORE(0));// 获取当前堆的结束地址. if (old_br == sp->base + sp->size) { // 开始收缩堆 char* rel_br = (char*)(CALL_MORECORE(-extra));// sbrk() 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);// 重新初始化top chunk. check_top_chunk(m, m->top); } } // end if (m->topsize > pad) /* Unmap any unused mmapped segments */ if (HAVE_MMAP) released += release_unused_segments(m); /* On failure, disable autotrim to avoid repeated failed future calls */ 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()释放内存。
- dlmalloc(四)
- dlmalloc解析连载四
- dlmalloc解析连载四
- dlmalloc(一)
- dlmalloc(二)
- dlmalloc(三)
- dlmalloc
- dlmalloc、nedmalloc
- dlmalloc源码
- dlmalloc 简析
- dlmalloc解析连载一
- dlmalloc解析连载三
- dlmalloc解析连载一
- dlmalloc解析连载二
- dlmalloc解析连载三
- DLmalloc 内存分配算法
- dlmalloc解析连载 (1)
- dlmalloc解析连载(2)
- 最常用的设计模式----单实例模式(C++ 实现)
- linux下测试读写速度的简单命令
- 归一化方法
- 实现UI中的布局设计
- [模拟]Jurassic
- dlmalloc(四)
- 细说Java IO相关
- 前端性能优化
- 小程序
- 设计模式C++实现(1)——工厂模式
- Java设计模式学习
- test
- Java NIO(1):迟迟登场的NIO
- asp.net算法——泛型转换DataTable