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

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

阅读更多

5.7 内存分配malloc()

 

Ptmalloc2 主要的内存分配函数为 malloc() ,但源代码中并不能找到该函数,该函数是用宏定义为 public_mALLOc() ,因为该函数在不同的编译条件下,具有不同的名称。 public_mALLOc() 函数只是简单的封装 _int_malloc() 函数, _int_malloc() 函数才是内存分配的核心实现。下面我们将分析 malloc 的实现。

 

5.7.1 public_mALLOc

    先给出源代码:

Void_t*
public_mALLOc(size_t bytes)
{
  mstate ar_ptr;
  Void_t *victim;

  __malloc_ptr_t (*hook) (size_t, __const __malloc_ptr_t)
    = force_reg (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));
首先检查是否存在内存分配的hook函数,如果存在,调用hook函数,并返回,hook函数主要用于进程在创建新线程过程中分配内存,或者支持用户提供的内存分配函数。请参考5.3节相关的描述。

  arena_lookup(ar_ptr);
  arena_lock(ar_ptr, bytes);
  if(!ar_ptr)
    return 0;
  victim = _int_malloc(ar_ptr, bytes);
获取分配区指针,如果获取分配区失败,返回退出,否则,调用_int_malloc()函数分配内存。

  if(!victim) {
    /* Maybe the failure is due to running out of mmapped areas. */
    if(ar_ptr != &main_arena) {
      (void)mutex_unlock(&ar_ptr->mutex);
      ar_ptr = &main_arena;
      (void)mutex_lock(&ar_ptr->mutex);
      victim = _int_malloc(ar_ptr, bytes);
      (void)mutex_unlock(&ar_ptr->mutex);
如果_int_malloc()函数分配内存失败,并且使用的分配区不是主分配区,这种情况可能是mmap区域的内存被用光了,当主分配区可以从堆中分配内存,所以需要再尝试从主分配区中分配内存。首先释放所使用分配区的锁,然后获得主分配区的锁,并调用_int_malloc()函数分配内存,最后释放主分配区的锁。

    } else {
#if USE_ARENAS
      /* ... or sbrk() has failed and there is still a chance to mmap() */
      ar_ptr = arena_get2(ar_ptr->next ? ar_ptr : 0, bytes);
      (void)mutex_unlock(&main_arena.mutex);
      if(ar_ptr) {
        victim = _int_malloc(ar_ptr, bytes);
        (void)mutex_unlock(&ar_ptr->mutex);
      }
如果_int_malloc()函数分配内存失败,并且使用的分配区是主分配区,查看是否有非主分配区,如果有,调用arena_get2()获取分配区,然后对主分配区解锁,如果arena_get2()返回一个非主分配区,尝试调用_int_malloc()函数从该非主分配区分配内存,最后释放该非主分配区的锁。
#endif
    }
  } else
(void)mutex_unlock(&ar_ptr->mutex);
如果_int_malloc()函数分配内存成功,释放所使用的分配区的锁。

  assert(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
         ar_ptr == arena_for_chunk(mem2chunk(victim)));
  return victim;
}

5.7.2 _int_malloc()

_int_malloc() 函数是内存分配的核心,根据分配的内存块的大小,该函数中实现了四种分配内存的路径,下面将分别分析这四种分配路径。

先给出 _int_malloc() 函数的函数定义及临时变量的定义:

static Void_t*
_int_malloc(mstate av, size_t bytes)
{
  INTERNAL_SIZE_T nb;               /* normalized request size */
  unsigned int    idx;              /* associated bin index */
  mbinptr         bin;              /* associated bin */

  mchunkptr       victim;           /* inspected/selected chunk */
  INTERNAL_SIZE_T size;             /* its size */
  int             victim_index;     /* its bin index */

  mchunkptr       remainder;        /* remainder from a split */
  unsigned long   remainder_size;   /* its size */

  unsigned int    block;            /* bit map traverser */
  unsigned int    bit;              /* bit map traverser */
  unsigned int    map;              /* current word of binmap */

  mchunkptr       fwd;              /* misc temp for linking */
  mchunkptr       bck;              /* misc temp for linking */

  const char *errstr = NULL;

  /*
    Convert request size to internal form by adding SIZE_SZ bytes
    overhead plus possibly more to obtain necessary alignment and/or
    to obtain a size of at least MINSIZE, the smallest allocatable
    size. Also, checked_request2size traps (returning 0) request sizes
    that are so large that they wrap around zero when padded and
    aligned.
  */
  checked_request2size(bytes, nb);

 checked_request2size() 函数将需要分配的内存大小 bytes 转换为需要分配的 chunk 大小 nb Ptmalloc 内部分配都是以 chunk 为单位,根据 chunk 的大小,决定如何获得满足条件的 chunk

 

5.7.2.1 分配fast bin chunk

如果所需的 chunk 大小小于等于 fast bins 中的最大 chunk 大小,首先尝试从 fast bins 中分配 chunk 。源代码如下:

/*
    If the size qualifies as a fastbin, first check corresponding bin.
    This code is safe to execute even if av is not yet initialized, so we
    can try it without checking, which saves some time on this fast path.
  */
  if ((unsigned long)(nb) <= (unsigned long)(get_max_fast ())) {
    idx = fastbin_index(nb);
    mfastbinptr* fb = &fastbin (av, idx);
#ifdef ATOMIC_FASTBINS
    mchunkptr pp = *fb;
    do
      {
        victim = pp;
        if (victim == NULL)
          break;
      }
    while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
           != victim);
#else
    victim = *fb;
#endif
    if (victim != 0) {
      if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
        {
          errstr = "malloc(): memory corruption (fast)";
        errout:
          malloc_printerr (check_action, errstr, chunk2mem (victim));
          return NULL;
        }
#ifndef ATOMIC_FASTBINS
      *fb = victim->fd;
#endif
      check_remalloced_chunk(av, victim, nb);
      void *p = chunk2mem(victim);
      if (__builtin_expect (perturb_byte, 0))
        alloc_perturb (p, bytes);
      return p;
    }
  }

 如果没有开启 ATOMIC_FASTBINS 优化,从 fast bins 中分配一个 chunk 相当简单,首先根据所需 chunk 的大小获得该 chunk 所属 fast bin index ,根据该 index 获得所需 fast bin 的空闲 chunk 链表的头指针,然后将头指针的下一个 chunk 作为空闲 chunk 链表的头部。为了加快从 fast bins 中分配 chunk ,处于 fast bins chunk 的状态仍然保持为 inuse 状态,避免被相邻的空闲 chunk 合并,从 fast bins 中分配 chunk ,只需取出第一个 chunk ,并调用 chunk2mem() 函数返回用户所需的内存块。

如果开启 ATOMIC_FASTBINS 优化,这里使用了 lock-free 的技术实现单向链表删除第一个 node 的操作。 Lock-free 算法的基础是 CAS (Compareand-Swap) 原子操作。当某个地址的原始值等于某个比较值时,把值改成新值,无论有否修改,返回这个地址的原始值。目前的 cpu 支持最多 64 位的 CAS ,并且指针 p 必须对齐。原子操作指一个 cpu 时钟周期内就可以完成的操作,不会被其他线程干扰。

一般的 CAS 使用方式是:假设有指针 p ,它指向一个 32 位或者 64 位数,

1.       复制 p 的内容( *p )到比较量 cmp (原子操作)。

2.       基于这个比较量计算一个新值 xchg (非原子操作)。

3.       调用 CAS 比较当前 *p cmp ,如果相等把 *p 替换成 xchg (原子操作)。

4.       如果成功退出,否则回到第一步重新进行。

3 步的 CAS 操作保证了写入的同时 p 没有被其他线程更改。如果 *p 已经被其他线程更改,那么第 2 步计算新值所使用的值( cmp )已经过期了,因此这个整个过程失败,重新来过。多线程环境下,由于 3 是一个原子操作,那么起码有一个线程(最快执行到 3 )的 CAS 操作可以成功,这样整体上看,就保证了所有的线程上在“前进”,而不需要使用效率低下的锁来协调线程,更不会导致死锁之类的麻烦。

ABA 问题,当 A 线程执行 2 的时候,被 B 线程更改了 *p x ,而 C 线程又把它改回了原始值,这时回到 A 线程, A 线程无法监测到原始值已经被更改过了, CAS 操作会成功(实际上应该失败)。 ABA 大部分情况下会造成一些问题,因为 p 的内容一般不可能是独立的,其他内容已经更改,而 A 线程认为它没有更改就会带来不可预知的结果。

如果开启 ATOMIC_FASTBINS 优化,这里的实现会出现 ABA 问题吗?不会出现,如果开启了 ATOMIC_FASTBINS 优化,在 free 时,如果释放的 chunk 属于 fast bin ,不需要对分配区加锁,可以通过 lock-free 技术将该 chunk 加入 fast bins 的链表中。当从分配区分配内存时,需要对分配区加锁,所以当 A 线程获得了分配区的锁,并从 fast bin 中分配内存执行 2 的时候,被 B 线程调用 free 函数向 fast bin 的链表中加入了一个新的 chunk ,即更改了 *fb x ,但不会存在 C 线程将 *fb 改回原值,如果存在,意味着 C 线程先分配了 *fb 所存的 chunk ,并将该 chunk 释放回了 fast bin ,但 C 线程分配 *fb 所存的 chunk 需要获得分配区的锁,但分配区的锁被 A 线程持有,所以 C 线程不可能将 *fb 改回原值,也就不会存在 ABA 问题。

 

 

分享到:
评论

相关推荐

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

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

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

    glibc内存管理ptmalloc源代码分析

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

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

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

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

    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调试器)是一款强大的调试工具,它...

    内存管理与调试详细剖析

    #### 五、源代码分析 ##### 5.1 边界标记法 Ptmalloc2采用了边界标记法来跟踪空闲内存块的边界,从而有效地管理内存。 ##### 5.2 分箱式内存管理 Ptmalloc2采用了一种称为分箱式内存管理的技术来组织不同大小的...

    malloc源码分析glibc库

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

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

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

    堆漏洞的利用技巧1

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

Global site tag (gtag.js) - Google Analytics