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()释放内存。