glibc 内存分配总结
1、下载和编译
源码下载:https://www.gnu.org/software/libc/
下载后解压:tar zxvf glibc-2.23.tar.gz
(原来这里下载了glibc-2.2.3.tar.gz,一个符号点之差,导致编译了半天不成功,各种错误)
$ tar zxvf glibc-2.23.tar.gz
$ mkdir glibc-build bin
设置环境变量bashrc
cat bashrc
export PATH=$PATH:/home/xxxx/mini_ctc_3.3/toolchain/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/
export PATH=$PATH:/home/xxxx/yodaos/openwrt/staging_dir/toolchain-arm_cortex-a7+neon_gcc-5.3.0_glibc-2.22_eabi/bin/
export CROSS_COMPILE=aarch64-linux-gnu-
export STAGING_DIR=/home/chenguolong/yodaos/openwrt/staging_dir/target-arm_cortex-a7+neon_glibc-2.22_eabi/
#export CROSS_COMPILE=arm-openwrt-linux-gnueabi-
export CC=${CROSS_COMPILE}gcc
export CPP=${CROSS_COMPILE}cppc
export CXX=${CROSS_COMPILE}g++
export LD=${CROSS_COMPILE}ld
export AR=${CROSS_COMPILE}ar
export STRIP=${CROSS_COMPILE}strip
export CFLAGS="-Wno-error=implicit-function-declaration -Wno-error=parentheses -O2"
export CMAKE_C_COMPILER=${CROSS_COMPILE}gcc
$ source bashrc
开始编译
$ cd glibc-build
$/home/xxxx/tmp/tools/glibc-2.23/configure --prefix=/home/chenguolong/tmp/tools/bin/ --host=aarch64-linux-gnu --disable-multilib libc_cv_forced_unwind=yes libc_cv_ssp=no libc_cv_ssp_strong=no
如果需要静态编译
$ ./configure CFLAGS="-static" ....
$ make LDFLAGS="-all-static"
其实编译开源软件都一样,--prefix即为install目录,--host为编译程序的二进制文件执行的主机
build:执行代码编译的主机,正常的话就是你的主机系统。这个参数一般由config.guess来猜就可以。当然自己指定也可以。
host:编译出来的二进制程序所执行的主机,因为绝大多数是如果本机编译,本机执行。所以这个值就等于build。只有交叉编译的时候(也就是本机编译,其他系统机器执行)才会build和host不同。用host指定运行主机。
target:这个选项只有在建立交叉编译环境的时候用到,正常编译和交叉编译都不会用到。他用build主机上的编译器,编译一个新的编译器(binutils, gcc,gdb等),这个新的编译器将来编译出来的其他程序将运行在target指定的系统上。
$ make -j32 && make install
2、Glibc即ptmalloc内存管理设计假设
Ptmalloc 在设计时折中了高效率,高空间利用率,高可用性等设计目标。在其实现代码中,隐藏着内存管理中的一些设计假设,由于某些设计假设,导致了在某些情况下 ptmalloc 的行为很诡异。这些设计假设包括:
1. 具有长生命周期的大内存分配使用 mmap。
2. 特别大的内存分配总是使用 mmap。
3. 具有短生命周期的内存分配使用 brk,因为用 mmap 映射匿名页,当发生缺页异常时,linux 内核为缺页分配一个新物理页,并将该物理页清 0,一个 mmap 的内存块需要映射多个物理页,导致多次清 0 操作,很浪费系统资源,所以引入了 mmap分配阈值动态调整机制,保证在必要的情况下才使用 mmap 分配内存。
4. 尽量只缓存临时使用的空闲小内存块,对大内存块或是长生命周期的大内存块在释放时都直接归还给操作系统。
5. 对空闲的小内存块只会在 malloc 和 free 的时候进行合并,free 时空闲内存块可能放入 pool 中,不一定归还给操作系统。
6. 收缩堆的条件是当前 free 的块大小加上前后能合并 chunk 的大小大于 64KB、,并且堆顶的大小达到阈值,才有可能收缩堆,把堆最顶端的空闲内存返回给操作系统。
7. 需要保持长期存储的程序不适合用 ptmalloc 来管理内存。
8. 为了支持多线程,多个线程可以从同一个分配区(arena)中分配内存,ptmalloc假设线程 A 释放掉一块内存后,线程 B 会申请类似大小的内存,但是 A 释放的内存跟 B 需要的内存不一定完全相等,可能有一个小的误差,就需要不停地对内存块作切割和合并,这个过程中可能产生内存碎片。
总结:
大块内存用mmap,耗时耗cpu
小块内存用malloc(brk),申请内存时并非立即真申请,释放时并非真释放
3、GLIBC 重要结构体和函数
chunk:
使用中chunk 空闲chunkbins
fast bins 快速查找的chunk
unsort bin 释放后规整
top chunk
Main_arena 主分配区,有且只有一个
non_main_arena 非主分配区,多个,多线程竞争使用
4、glibc 内存暴增原因
1、后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相
邻的 chunk 不能释放,top chunk 以下的 chunk 都无法释放。
2. Ptmalloc 不适合用于管理长生命周期的内存,特别是持续不定期分配和释放长生命周期
的内存,这将导致 ptmalloc 内存暴增。如果要用 ptmalloc 分配长周期内存,在 32 位系
统上,分配的内存块最好大于 1MB,64 位系统上,分配的内存块大小大于 32MB。这是
由于 ptmalloc 默认开启 mmap 分配阈值动态调整功能,1MB 是 32 位系统 mmap 分配阈
值的最大值,32MB 是 64 位系统 mmap 分配阈值的最大值,这样可以保证 ptmalloc 分配
的内存一定是从 mmap 映射区域分配的,当 free 时,ptmalloc 会直接把该内存返回给操
作系统,避免了被 ptmalloc 缓存。
3. 不要关闭 ptmalloc 的 mmap 分配阈值动态调整机制,因为这种机制保证了短生命周期的
内存分配尽量从 ptmalloc 缓存的内存 chunk 中分配,更高效,浪费更少的内存。如果关
闭了该机制,对大于 128KB 的内存分配就会使用系统调用 mmap 向操作系统分配内存,
使用系统调用分配内存一般会比从 ptmalloc 缓存的 chunk 中分配内存慢,特别是在多线
程同时分配大内存块时,操作系统会串行调用 mmap(),并为发生缺页异常的页加载新
物理页时,默认强制清 0。频繁使用 mmap 向操作系统分配内存是相当低效的。使用
mmap 分配的内存只适合长生命周期的大内存块。
4. 多线程分阶段执行的程序不适合用 ptmalloc,这种程序的内存更适合用内存池管理,就
像 Appach 那样,每个连接请求处理分为多个阶段,每个阶段都有自己的内存池,每个
阶段完成后,将相关的内存就返回给相关的内存池。Google 的许多应用也是分阶段执行
的,他们在使用 ptmalloc 也遇到了内存暴增的相关问题,于是他们实现了 TCMalloc 来代
替 ptmalloc,TCMalloc 具有内存池的优点,又有垃圾回收的机制,并最大限度优化了锁
的争用,并且空间利用率也高于 ptmalloc。Ptmalloc 假设了线程 A 释放的内存块能在线
程 B 中得到重用,但 B 不一定会分配和 A 线程同样大小的内存块,于是就需要不断地做
切割和合并,可能导致内存碎片。
5. 尽量减少程序的线程数量和避免频繁分配/释放内存,Ptmalloc 在多线程竞争激烈的情况
下,首先查看线程私有变量是否存在分配区,如果存在则尝试加锁,如果加锁不成功会
尝试其它分配区,如果所有的分配区的锁都被占用着,就会增加一个非主分配区供当前
线程使用。由于在多个线程的私有变量中可能会保存同一个分配区,所以当线程较多时,
加锁的代价就会上升,ptmalloc 分配和回收内存都要对分配区加锁,从而导致了多线程
竞争环境下 ptmalloc 的效率降低。
6. 防止内存泄露,ptmalloc 对内存泄露是相当敏感的,根据它的内存收缩机制,如果与 top
chunk 相邻的那个 chunk 没有回收,将导致 top chunk 一下很多的空闲内存都无法返回给
操作系统。
7. 防止程序分配过多内存,或是由于 Glibc 内存暴增,导致系统内存耗尽,程序因 OOM 被
系 统 杀 掉 。 预 估 程 序 可 以 使 用 的 最 大 物 理 内 存 大 小 , 配 置 系 统 的
/proc/sys/vm/overcommit_memory,/proc/sys/vm/overcommit_ratio,以及使用 ulimt –v
限制程序能使用虚拟内存空间大小,防止程序因 OOM 被杀掉。
5、GLBIC 申请或者收缩(释放)域值调整
1. M_MXFAST
M_MXFAST 用于设置 fast bins 中保存的 chunk 的最大大小,默认值为 64B,fast bins 中
保存的 chunk 在一段时间内不会被合并,分配小对象时可以首先查找 fast bins,如果 fast bins
找到了所需大小的 chunk,就直接返回该 chunk,大大提高小对象的分配速度,但这个值设
置得过大,会导致大量内存碎片,并且会导致 ptmalloc 缓存了大量空闲内存,去不能归还给
操作系统,导致内存暴增。
2. M_TRIM_THRESHOLD
M_TRIM_THRESHOLD 用于设置 mmap 收缩阈值,默认值为 128KB。自动收缩只会在 free
时才发生,如果当前 free 的 chunk 大小加上前后能合并 chunk 的大小大于 64KB,并且 top
chunk 的大小达到 mmap 收缩阈值,对于主分配区,调用 malloc_trim()返回一部分内存给操
作系统,对于非主分配区,调用 heap_trim()返回一部分内存给操作系统,在发生内存收缩
时,还是从新设置 mmap 分配阈值和 mmap 收缩阈值。
3. M_MMAP_THRESHOLD
M_MMAP_THRESHOLD 用于设置 mmap 分配阈值,默认值为 128KB,ptmalloc 默认开启
动态调整 mmap 分配阈值和 mmap 收缩阈值。
4. M_MMAP_MAX
M_MMAP_MAX 用于设置进程中用 mmap 分配的内存块的最大限制,默认值为 64K,因
为有些系统用 mmap 分配的内存块太多会导致系统的性能下降。
代码设置:
include <malloc.h>
mallopt(M_TRIM_THRESHOLD, 32 * 1024);
mallopt(M_MMAP_THRESHOLD, 64 * 1024);