`
mqzhuang
  • 浏览: 187574 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Glibc内存管理--ptmalloc2源代码分析(二十七)

 
阅读更多

5.7.2.3 分配large bin chunk(一)

如果所需的 chunk 不属于 small bins ,首先会执行如下的代码段:

/*
     If this is a large request, consolidate fastbins before continuing.
     While it might look excessive to kill all fastbins before
     even seeing if there is space available, this avoids
     fragmentation problems normally associated with fastbins.
     Also, in practice, programs tend to have runs of either small or
     large requests, but less often mixtures, so consolidation is not
     invoked all that often in most programs. And the programs that
     it is called frequently in otherwise tend to fragment.
  */

  else {
    idx = largebin_index(nb);
    if (have_fastchunks(av))
      malloc_consolidate(av);
  }

 所需chunk 不属于 small bins ,那么就一定属于 large bins ,首先根据 chunk 的大小获得对应的 large bin index ,接着判断当前分配区的 fast bins 中是否包含 chunk ,如果存在,调用 malloc_consolidate() 函数合并 fast bins 中的 chunk ,并将这些空闲 chunk 加入 unsorted bin 中。

下面的源代码实现从 last remainder chunk large bins top chunk 中分配所需的 chunk ,这里包含了多个多层循环,在这些循环中,主要工作是分配前两步都未分配成功的 small bin chunk large bin chunk large chunk 。最外层的循环用于重新尝试分配 small bin chunk ,因为如果在前一步分配 small bin chunk 不成功,并没有调用 malloc_consolidate() 函数合并 fast bins 中的 chunk ,将空闲 chunk 加入 unsorted bin 中,如果第一尝试从 last remainder chunk top chunk 中分配 small bin chunk 都失败以后,如果 fast bins 中存在空闲 chunk ,会调用 malloc_consolidate() 函数,那么在 usorted bin 中就可能存在合适的 small bin chunk 供分配,所以需要再次尝试。

 /*
    Process recently freed or remaindered chunks, taking one only if
    it is exact fit, or, if this a small request, the chunk is remainder from
    the most recent non-exact fit.  Place other traversed chunks in
    bins.  Note that this step is the only place in any routine where
    chunks are placed in bins.

    The outer loop here is needed because we might not realize until
    near the end of malloc that we should have consolidated, so must
    do so and retry. This happens at most once, and only when we would
    otherwise need to expand memory to service a "small" request.
  */
  for(;;) {
    int iters = 0;
while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
反向遍历unsorted bin的双向循环链表,遍历结束的条件是循环链表中只剩下一个头结点。

      bck = victim->bk;
      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));
      size = chunksize(victim);

 检查当前遍历的 chunk 是否合法, chunk 的大小不能小于等于 2 * SIZE_SZ ,也不能超过该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size 。这里的检查似乎有点小问题,直接使用了 victim->size ,但 victim->size 中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在 unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接 victim->size 没有问题。

 /*
         If a small request, try to use last remainder if it is the
         only chunk in unsorted bin.  This helps promote locality for
         runs of consecutive small requests. This is the only
         exception to best-fit, and applies only when there is
         no exact fit for a small chunk.
      */
      if (in_smallbin_range(nb) &&
          bck == unsorted_chunks(av) &&
          victim == av->last_remainder &&
          (unsigned long)(size) > (unsigned long)(nb + MINSIZE)) {

 如果需要分配一个 small bin chunk ,在 5.7.2.2 节中的 small bins 中没有匹配到合适的 chunk ,并且 unsorted bin 中只有一个 chunk ,并且这个 chunk last remainder chunk ,并且这个 chunk 的大小大于所需 chunk 的大小加上 MINSIZE ,在满足这些条件的情况下,可以使用这个 chunk 切分出需要的 small bin chunk 。这是唯一的从 unsorted bin 中分配 small bin chunk 的情况,这种优化利于 cpu 的高速缓存命中。

 /* 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;
          }

chunk 中切分出所需大小的 chunk ,计算切分后剩下 chunk 的大小,将剩下的 chunk 加入 unsorted bin 的链表中,并将剩下的 chunk 作为分配区的 last remainder chunk ,若剩下的 chunk 属于 large bin chunk ,将该 chunk fd_nextsize bk_nextsize 设置为 NULL ,因为这个 chunk 仅仅存在于 unsorted bin 中,并且 unsorted bin 中有且仅有这一个 chunk

       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);
        if (__builtin_expect (perturb_byte, 0))
          alloc_perturb (p, bytes);
        return p;

 设置分配出的 chunk last remainder chunk 的相关信息,如 chunk size ,状态标志位,对于 last remainder chunk ,需要调用 set_foot 宏,因为只有处于空闲状态的 chunk foot 信息( prev_size )才是有效的,处于 inuse 状态的 chunk foot 无效,该 foot 是返回给应用层的内存块的一部分。设置完成 chunk 的相关信息,调用 chunk2mem() 获得 chunk 中可用的内存指针,返回给应用层,退出。

      }

      /* remove from unsorted list */
      unsorted_chunks(av)->bk = bck;
      bck->fd = unsorted_chunks(av);
将双向循环链表中的最后一个chunk移除。

      /* 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);
        if (__builtin_expect (perturb_byte, 0))
          alloc_perturb (p, bytes);
        return p;
      }

 如果当前遍历的 chunk 与所需的 chunk 大小一致,将当前 chunk 返回。首先设置当前 chunk 处于 inuse 状态,该标志位处于相邻的下一个 chunk size 中,如果当前分配区不是主分配区,设置当前 chunk 的非主分配区标志位,最后调用 chunk2mem() 获得 chunk 中可用的内存指针,返回给应用层,退出。

      /* place chunk in bin */
      if (in_smallbin_range(size)) {
        victim_index = smallbin_index(size);
        bck = bin_at(av, victim_index);
        fwd = bck->fd;
如果当前chunk属于small bins,获得当前chunk所属small bin的index,并将该small bin的链表表头赋值给bck,第一个chunk赋值给fwd,也就是当前的chunk会插入到bck和fwd之间,作为small bin链表的第一个chunk。

      }
      else {
        victim_index = largebin_index(size);
        bck = bin_at(av, victim_index);
        fwd = bck->fd;
如果当前chunk属于large bins,获得当前chunk所属large bin的index,并将该large bin的链表表头赋值给bck,第一个chunk赋值给fwd,也就是当前的chunk会插入到bck和fwd之间,作为large bin链表的第一个chunk。

        /* 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);

 如果fwd 不等于 bck ,意味着 large bin 中有空闲 chunk 存在,由于 large bin 中的空闲 chunk 是按照大小顺序排序的,需要将当前从 unsorted bin 中取出的 chunk 插入到 large bin 中合适的位置。将当前 chunk size inuse 标志 bit 置位,相当于加 1 ,便于加快 chunk 大小的比较,找到合适的地方插入当前 chunk 。这里还做了一次检查,断言在 large bin 双向循环链表中的最后一个 chunk size 字段中的非主分配区的标志 bit 没有置位,因为所有在 large bin 中的 chunk 都处于空闲状态,该标志位一定是清零的。

         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;

 如果当前 chunk large bin 的最后一个 chunk 的大小还小,那么当前 chunk 就插入到 large bin 的链表的最后,作为最后一个 chunk 。可以看出 large bin 中的 chunk 是按照从大到小的顺序排序的,同时一个 chunk 存在于两个双向循环链表中,一个链表包含了 large bin 中所有的 chunk ,另一个链表为 chunk size 链表,该链表从每个相同大小的 chunk 的取出第一个 chunk 按照大小顺序链接在一起,便于一次跨域多个相同大小的 chunk 遍历下一个不同大小的 chunk ,这样可以加快在 large bin 链表中的遍历速度。

         }
          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);
              }
	正向遍历chunk size链表,直到找到第一个chunk大小小于等于当前chunk大小的chunk退出循环。

            if ((unsigned long) size == (unsigned long) fwd->size)
              /* Always insert in the second position.  */
              fwd = fwd->fd;
如果从large bin链表中找到了与当前chunk大小相同的chunk,则同一大小的chunk已经存在,那么chunk size链表中一定包含了fwd所指向的chunk,为了不修改chunk size链表,当前chunk只能插入fwd之后。

            else
              {
                victim->fd_nextsize = fwd;
                victim->bk_nextsize = fwd->bk_nextsize;
                fwd->bk_nextsize = victim;
                victim->bk_nextsize->fd_nextsize = victim;
如果chunk size链表中还没有包含当前chunk大小的chunk,也就是说当前chunk的大小大于fwd的大小,则将当前chunk作为该chunk size的代表加入chunk size链表,chunk size链表也是按照由大到小的顺序排序。

              }
            bck = fwd->bk;
          }
        } else
          victim->fd_nextsize = victim->bk_nextsize = victim;
如果large bin链表中没有chunk,直接将当前chunk加入chunk size链表。
      }

      mark_bin(av, victim_index);
      victim->bk = bck;
      victim->fd = fwd;
      fwd->bk = victim;
      bck->fd = victim;
上面的代码将当前chunk插入到large bin的空闲chunk链表中,并将large bin所对应binmap的相应bit置位。

#define MAX_ITERS       10000
      if (++iters >= MAX_ITERS)
        break;
如果unsorted bin中的chunk超过了10000个,最多遍历10000个就退出,避免长时间处理unsorted bin影响内存分配的效率。
    }

当将unsorted bin中的空闲chunk加入到相应的small bins和large bins后,将使用最佳匹配法分配large bin chunk。源代码如下:
    /*
      If a large request, scan through the chunks of current bin in
      sorted order to find smallest that fits.  Use the skip list for this.
    */
    if (!in_smallbin_range(nb)) {
      bin = bin_at(av, idx);

      /* skip scan if empty or largest chunk is too small */
      if ((victim = first(bin)) != bin &&
          (unsigned long)(victim->size) >= (unsigned long)(nb)) {
如果所需分配的chunk为large bin chunk,查询对应的large bin链表,如果large bin链表为空,或者链表中最大的chunk也不能满足要求,则不能从large bin中分配。否则,遍历large bin链表,找到合适的chunk。

        victim = victim->bk_nextsize;
        while (((unsigned long)(size = chunksize(victim)) <
                (unsigned long)(nb)))
          victim = victim->bk_nextsize;
反向遍历chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环。

        /* 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;

 如果 large bin 链表中选取的 chunk victim 不是链表中的最后一个 chunk ,并且与 victim 大小相同的 chunk 不止一个,那么意味着 victim chunk size 链表中的节点,为了不调整 chunk size 链表,需要避免将 chunk size 链表中的节点取出,所以取 victim->fd 节点对应的 chunk 作为候选 chunk 。由于 large bin 链表中的 chunk 也是按大小排序,同一大小的 chunk 有多个时,这些 chunk 必定排在一起,所以 victim->fd 节点对应的 chunk 的大小必定与 victim 的大小一样。

        remainder_size = size - nb;
        unlink(victim, bck, fwd);
计算将victim切分后剩余大小,并调用unlink()宏函数将victim从large bin链表中取出。

        /* Exhaust */
        if (remainder_size < MINSIZE)  {
          set_inuse_bit_at_offset(victim, size);
          if (av != &main_arena)
            victim->size |= NON_MAIN_ARENA;

 如果 victim 切分后剩余大小小于 MINSIZE ,则将真个 victim 分配给应用层,这种情况下,实际分配的 chunk 比所需的 chunk 要大一些。以 64 位系统为例, remainder_size 的可能大小为 0 16 ,如果为 0 ,表示 victim 的大小刚好等于所需 chunk 的大小,设置 victim inuse 标志, inuse 标志位于下一个相邻的 chunk size 字段中。如果 remainder_size 16 ,则这 16 字节就浪费掉了。 如果当前分配区不是主分配区, victim size 字段中的非主分配区标志置位。

        }
        /* Split */
        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 (__builtin_expect (fwd->bk != bck, 0))
            {
              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;
            }
从victim中切分出所需的chunk,剩余部分作为一个新的chunk加入到unsorted bin中。如果剩余部分chunk属于large bins,将剩余部分chunk的chunk size链表指针设置为NULL,因为unsorted bin中的chunk是不排序的,这两个指针无用,必须清零。

          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);
设置victim和remainder的状态,由于remainder为空闲chunk,所以需要设置该chunk的foot。

        }
        check_malloced_chunk(av, victim, nb);
        void *p = chunk2mem(victim);
        if (__builtin_expect (perturb_byte, 0))
          alloc_perturb (p, bytes);
        return p;
	从large bin中使用最佳匹配法找到了合适的chunk,调用chunk2mem()获得chunk中可用的内存指针,返回给应用层,退出。
      }
    }

 

 

 

 

 

0
0
分享到:
评论

相关推荐

    Glibc内存管理--ptmalloc2源代码分析(三十四)

    《Glibc内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...

    glibc内存管理ptmalloc源代码分析PDF

    在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...

    glibc内存管理ptmalloc源代码分析-高清PDF-pdf版

    《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...

    glibc内存管理ptmalloc源代码分析.pdf

    glibc内存管理ptmalloc源代码分析

    glibc内存管理ptmalloc源代码分析@华庭1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...

    Glibc内存管理Ptmalloc2源代码分析

    标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...

    glibc内存管理ptmalloc源代码分析4.pdf

    标题与描述概述的知识点主要集中在glibc内存管理的细节,特别是ptmalloc的源代码分析,这对于深入理解C库如何高效地处理内存分配和回收至关重要。以下是对这些知识点的详细解析: ### Glibc内存管理与ptmalloc ###...

    glibc内存管理ptmalloc源代码分析1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...

    glibc内存管理ptmalloc源代码分析

    ### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...

    glibc内存管理ptmalloc源代码分析-清晰版.pdf

    ### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...

    Python-用于检测glibc堆ptmalloc的gdbpython库

    `glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...

    内存管理与调试详细剖析

    本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...

    malloc源码分析glibc库

    通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...

    pwn学习总结(八)—— 堆(持续更新)

    学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...

    堆漏洞的利用技巧1

    《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。

Global site tag (gtag.js) - Google Analytics