Linux内存管理(三)–内存分配之malloc
本文主要介绍linux动态内存分配流程。
传送上文:Linux内存管理(二)–进程地址空间2linux5.15glibc-2.37
动态内存分配之 malloc
开源社区公开了很多现成的内存分配器(Memory Allocators,以下简称为分配器):dlmalloc – 第一个被广泛使用的通用动态内存分配器;ptmalloc2 – glibc 内置分配器的原型;jemalloc – FreeBSD & Firefox 所用分配器;tcmalloc – Google 贡献的分配器;libumem – Solaris 所用分配器;
glibc 中使用的 ptmalloc2 是基于dlmalloc 开发,并引入了多线程支持。
malloc源码位置glibc-2.37\malloc\malloc.c,malloc 就是 __libc_malloc 的别名
核心函数路径 _int_malloc -> sysmalloc,会根据mmap_threshold 决定是采用brk还是mmap 系统调用去分配内存,默认的mmap_threadshold为128KB。
调用mmap分配
当需要分配的内存大小超过mmap_threadshold时采用mmap分配
调用brk分配
当需要分配的内存大小小于mmap_threadshold时采用brk分配
malloc 用到的核心数据结构
但是为了效率,malloc并不是每次分配都会去调用系统调用的,采用池化思想,每次需要调用系统调用时就多分配一些,方便后续申请可以直接返回。
Arena
在内存中,我们称一个连续的堆区域为 arena,有main arena 和 thread arena。因为malloc可以支持多线程,但是每个线程对应一个 thread arena的开销太高了,所以线程对应的 thread arena数量限制
当线程数大于arena数量时,就会存在arena被线程共享的情况
举例而言:让我们来看一个运行在单核计算机上的 32 位操作系统上的多线程应用(4 线程,主线程 + 3 个线程)的例子。这里线程数量(4)> 2 * 核心数(1),所以分配器中可能有 Arena(也即标题所称「multiple arenas」)会被所有线程共享。那么是如何共享的呢?
首次:当主线程第一次调用malloc时,已经建立的 main arena 会被没有任何竞争地使用;当 thread 1 和 thread 2 第一次调用malloc时,一块新的 arena 将被创建,且将被没有任何竞争地使用。此时线程和 arena 之间存在一一映射关系;当 thread3 第一次调用malloc时,arena 的数量限制被计算出来,结果显示已超出,因此尝试复用已经存在的 arena(也即 Main arena 或 Arena 1 或 Arena 2);
复用:一旦遍历到可用 arena,就开始自旋申请该 arena 的锁;如果上锁成功(比如说 main arena 上锁成功),就将该 arena 返回用户;如果没找到可用 arena,thread 3 的malloc将被阻塞,直到有可用的 arena 为止。当thread 3 调用malloc时(第二次了),分配器会尝试使用上一次使用的 arena(也即,main arena),从而尽量提高缓存命中率。当 main arena 可用时就用,否则 thread 3 就一直阻塞,直至 main arena 空闲。因此现在 main arena 实际上是被 main thread 和 thread 3 所共享。
作为 arena 的 header,被 thread arena 维护的多个堆共享。
main arena 无需维护多个堆,因此也无需 heap_info。当空间耗尽时,与 thread arena 不同,main arena 可以通过sbrk拓展堆段,直至堆段碰到内存映射段;main arena 的 arena header 不是保存在通过sbrk申请的堆段里,而是作为一个全局变量,可以在 libc.so 的数据段中找到。
heap_info
包含一个堆的元数据。通常情况下,一个 thread arena 维护一个堆,但是当这个堆空间耗尽时,新的堆就会被映射到这个thread arena中。
chunk
用于描述一个chunk。
:若上一个 chunk 可用,则此字段赋值为上一个 chunk 的大小;否则,此字段被用来存储上一个 chunk 的用户数据;
:此字段赋值本 chunk 的大小,其最后三位包含标志信息: – 置「1」表示上个 chunk 被分配。 – 置「1」表示这个 chunk 是通过mmap申请的(较大的内存)。 – 置「1」表示这个 chunk 属于一个 thread arena。用户请求的大小被转换为内部实际大小,因为需要额外空间存储 malloc_chunk,此外还需要考虑对齐。
堆段中存在的 chunk 类型如下Allocated chunkFree chunkTop chunkLast Remainder chunk
Allocated chunk
chunk:该 Allocated chunk 的起始地址;mem:该 Allocated chunk 中用户可用区域的起始地址(chunk + sizeof(malloc_chunk));next_chunk:下一个 chunk(无论类型)的起始地址。
Free chunk
: 两个相邻 free chunk 会被合并成一个,因此该字段总是保存前一个 allocated chunk 的用户数据: 该字段保存本 free chunk 的大小: Forward pointer, 本字段指向同一 bin 中的下个 free chunk: Backward pointer, 本字段指向同一 bin 中的上个 free chunk
Top chunk
一个 arena 中最顶部的 chunk 被称为。它不属于任何 bin 。当所有 bin 中都没有合适空闲内存时,就会使用 top chunk 来响应用户请求。
当 的大小比用户请求的大小大的时候, 会分割为两个部分:,返回给用户,剩余部分,将成为新的 。
当的大小比用户请求的大小小的时候,top chunk 就通过sbrk(main arena)或mmap( thread arena)系统调用扩容。
Last Remainder chunk
即最后一次请求中因分割而得到的剩余部分,它有利于改进引用局部性,也即后续对 malloc请求可能最终被分配得彼此靠近。
小结
我们把上面提到的三种结构串起来,得到 malloc 组织内存的形式如下
多堆段 thread arena 如下
关于malloc中的管理可参考「malloc 是如何管理 free chunk 的」,关于brk与mmap调用详解,请看下回分解「Linux内存分配之brk与mmap」。
更多内容可参考专栏: 嵌入式Linux笔记。
转载请注明出处!
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/93074.html