动态内存的分配和释放最重要的就是malloc 和 free 这两个函数 一个是用于向操作系统索取动态的内存空间 一个是用于释放之前通过malloc或者relloc函数分配的空间。其内部通过brk,sbrk和mmap实现对内存的索取。
举个例子 当malloc(1024)的时候 即我们只需要1024Bytes,但是进程会向操作系统先批发一块大的内存块,又因为是主线程所以称为main arena 它是一块非常大的连续内存区域 紧邻在bss,即未初始化数据的下方 下面的图直接展示了它是在主线程的下方 开辟了132KB大的空间。之后的再次提交malloc之类的动态内存申请的时候,就会先使用该arena直到消耗完这片连续的内存区域。
当消耗完该块arena后,程序可以通过增加相应的break location 即数据段高度(通过brk()和sbrk()实现)并且会伴随着Top Chunk的变化
当使用free函数释放内存的时候 堆区域并不会立即被释放掉 而是会被添加这个被free的区块到main arena的bin中 在glibc中 释放所需的数据结构跟bin这个数据结构有关。 当之后用户再次请求动态内存的时候就不必先向操作系统提交请求,而是先从bin中找有木有合适的chunk,当区块均不合适的时候,才会向操作系统提交申请
多线程下的heap段
之前我们分析了主线程第一次进行malloc的时候以及进行free时候的情形。现在在多线程的情况下 ,分析一下在主线程之外创建额外线程的时候,额外线程使用malloc时候的情形。此时虽然线程未创建 但是对应的线程堆栈是存在的
额外线程在创建heap之后的情形 其向操作系统申请的堆区域是0xb7500000-0xb7521000 也是132KB的大小,由于是在额外线程 所以这块大区域我们称之为thread arena。额外线程使用动态内存分配函数的时候 其内部并不是像main arena一样使用brk()或者sbrk(),而是使用mmap这个系统调用来分配一块区域,并且令人注意的是 我们从图中发现的是其实mmap一开始映射一块1MB的内存空间,但是只有132KB是拥有读写属性的,这132KB才是真正的Thread Arena。
当用户释放掉在thread arena的缓冲区时,操作系统也不会立即回收这块内存,而是也将这个freed chunk加入到对应的thread arena中供以后分配使用
mmap额外
当用户申请一次性申请超大内存的时候 不论此时是处于哪个线程 都会用mmap来映射一块内存到该进程空间中返回给用户使用
Number of Arena
对于Arena的数量 其应用程序的arena数量自然不是一个线程一个arena 这样显得非常浪费和无用,其arena的最大数量是基于cpu的核心数来决定的
Number of arena’s: In above example, we saw main thread contains main arena and thread 1 contains its own thread arena. So can there be a one to one mapping between threads and arena, irrespective of number of threads? Certainly not. An insane application can contain more number of threads (than number of cores), in such a case, having one arena per thread becomes bit expensive and useless. Hence for this reason, application’s arena limit is based on number of cores present in the system.
具体的arena数量见下 32位系统下arena数量是核心的两倍 64位系统下则是核心的8倍
For 32 bit systems:
Number of arena = 2 * number of cores.
For 64 bit systems:
Number of arena = 8 * number of cores.
多线程下Arena的分配使用
当线程的数量大于最大Arena数量的时候,就需要进行一个共享操作 具体流程见下 这里我们以在32位机子上只有一个cpu核心 而程序运行着4个线程为例
- 对于main arena,即主线程使用该arena的时候 显然是不需要进行任何犹豫和同意的
- 当线程1和线程2第一次使用malloc的时候 此时为他们每个线程创建一个arena
- 当第三个线程第一次使用malloc的时候 此时最大arena数量已经达到了 所以需要进行reuse重用 重用流程如下
- 首先将链表头设置为main_arena
- 然后遍历整个arena链表 找出第一个能加锁 成功加锁的时候mutex_trylock返回的是0
- 如果找不到未加锁的arena 则阻塞等待
- 当线程3第二次使用malloc的时候 会使用最近一次malloc的arena 若该arena已经加锁 则线程进行阻塞等到其释放空间
来源:https://blog.csdn.net/qq_40890756/article/details/100834246