这篇文章我们来讲讲释放内存的过程,也就是free()的代码流程。对于应用程序来说释放内存很简单,直接调用free(ptr)就可以了,参数是要释放的内存块指针。那么,释放内存时dlmalloc做了哪些工作呢?

// 这是释放内存的函数,调用free()后执行到这里.
// 参数mem: 这是将要释放内存的指针
void dlfree(void* mem) {/*Consolidate freed chunks with preceeding or succeeding borderingfree chunks, if they exist, and then place in a bin.  Intermixedwith 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) {  // 如果不是dvunlink_chunk(fm, p, prevsize); // 将这个内存块从malloc_state结构中删除.}// 如果是dvelse if ((next->head & INUSE_BITS) == INUSE_BITS) {  // 后面一个内存块在使用中,那么就处理完毕了.fm->dvsize = psize;   // 修改这个chunk的长度.set_free_with_pinuse(p, psize, next);goto postaction;   // 处理完毕}// 如果后面一个内存块也是空间的,那么还需要将后面一个内存块合并到dv中.}elsegoto 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(四)相关推荐

  1. dlmalloc 图解

    2019独角兽企业重金招聘Python工程师标准>>> 2. 标记结构 本章节将介绍基本的内存标记结构,包括chunk, tree chunk, sbin, tbin, segmen ...

  2. c/c++的内存四区

    内存四区的图示 内存四区的代码案例 #include <stdio.h> void fun() {static int k = 10; //初始化的静态局部变量(data区的rw段)sta ...

  3. TCP三次握手和四次挥手的解释

    基础知识 在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG. 其中,对于我们日常的分析有用的就是前面的五个字段. 它们的含义是: SYN ...

  4. C++核心编程(四)--文件操作

    5 文件操作 程序运行时产生的数据都属于临时数据,程序一点运行结束,就会被释放 通过文件可以将数据持久化 C++中对文件操作需要包含头文件:fstream 文件类型分为两种: 文本文件:文件以文本的A ...

  5. 受用一生的高效 PyCharm 使用技巧(四)

    https://blog.csdn.net/pdcfighting/article/details/93269028 大家好,距离最近一篇 PyCharm 使用技巧的文章已经过去一月有余,最近虽然也比 ...

  6. Python学习(四)cPickle的用法

    python中有两个类似的:pickle与cPickle:两者的关系:"cPickle – A faster pickle" pickle模块中的两个主要函数是dump()和loa ...

  7. jieba中文分词源码分析(四)

    一.未登录词问题 在jieba中文分词的第一节曾提到未登录词问题 中文分词的难点 分词规范,词的定义还不明确 (<统计自然语言处理>宗成庆) 歧义切分问题,交集型切分问题,多义组合型切分歧 ...

  8. 操作系统学习笔记 第四章:存储器管理(王道考研)

    本文章基于网课: 2019 王道考研 操作系统 考试复习推荐资料:操作系统复习总结 - 百度文库 (baidu.com) 需要相关电子书的可以关注我的公众号BaretH后台回复操作系统 第一章:操作系 ...

  9. 王道考研 计算机网络笔记 第四章:网络层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

最新文章

  1. Android--百度地图密钥申请+环境配置(一)
  2. Linux环境安装canvas,npm install canvas简明指南
  3. 设计模式:享元(FlyWeight)模式
  4. simulink积分模块和微分模块区别
  5. boost::geometry::geometry_id用法的测试程序
  6. FCN-加载训练与测试数据
  7. Go语言内部包--控制包成员的对外暴露
  8. Java 并发(生产者/消费者 模式)
  9. 给开源项目贡献代码的经历
  10. 一分钟在云端快速创建MySQL数据库实例
  11. 【导入篇】Robotics:Perception课程_导入篇、四周课程内容、week 1st Perspective Projection
  12. .NET程序不需要受SVN版本控制的文件类型
  13. NB50/60 TJ1/TK1 模具 黑苹果保姆级教程整理
  14. Js设置Cookie
  15. Java常见练习题总结
  16. 记录这一刻:开通原创保护功能
  17. 物联网应用之现代档案馆环境智能化监控系统解决方案
  18. qpython爬虫_python爬虫教程:批量抓取 QQ 群信息
  19. echarts绘制百家姓饼状图
  20. ubuntu连接xp的共享打印机

热门文章

  1. Muv-Luv Alternative分析文档 Part 〇—四
  2. 梦幻西游三维获取服务器信息,梦幻西游三维版:潜能果上线后经验紧缺?五分钟教你快速获得经验...
  3. cad转换pdf怎么转换?
  4. DFM实例分享-替代料审查
  5. 【逆向工程】x64dbg逆向扫雷及QT编写游戏辅助
  6. duolinguo考试时摄像头/麦克风无法使用
  7. 风控红宝书重磅上市!技术和业务双维度揭秘风控与反欺诈
  8. 北京物资学院图书馆打印个人PC的文件上传方式
  9. PTA 6-2 多项式的求值-C语言实现
  10. 手机版PDF编辑器支持PDF转Word、文档内容编辑合并与提取