• Linux高级调试与优化——用户态堆


    内存问题是软件世界的住房问题

      嵌入式Linux系统中,物理内存资源通常比较紧张,而不同的进程可能不停地分配和释放不同大小的内存,因此需要一套高效的内存管理机制。

      内存管理可以分为三个层次,自底向上分别为:

      1)Linux内核内存管理;

      2)glibc层使用系统调用(brk/sbrk)维护的内存管理算法;即glibc库维护一个内存资源池,在应用层满足其他应用的需求。

         glibc库使用第三方的ptmalloc库实现用户态堆管理器,当应用程序调用glibc库封装的malloc函数申请内存时,首先在用户态内存资源池中找合适的块,但是如果申请的内存大小超过128KB,则直接通过brk系统调用向内核申请物理内存。

      3)应用程序从glibc库动态分配内存后,根据应用程序本身的程序特性进行优化。即应用程序分配超大内存块,自己内部实现内存分配算法。

      需要明确的一点是,malloc函数和free函数不是系统调用,而是glibc库封装的接口,malloc和free都是通过brk系统调用实现。

    Linux进程地址空间

      典型的Linux进程地址空间(虚拟地址)布局如下图,栈从上往下增长,堆从下往上增长。

      对于32位Linux系统而言,其地址总线宽度为32位,4字节对齐,可寻址范围为4GB。进程地址空间中高1GB为内核地址空间,低3GB为用户地址空间。

      对于64位Linux系统而言,其地址总线通常为48位,8字节对齐,可寻址范围为256TB。进程地址空间中高128TB为内核地址空间,低128TB为用户地址空间。

      栈一般是函数调用栈,函数调用时,将父函数的局部变量、临时变量、LR和PC寄存器值压栈,并为子函数创建新的栈帧。堆则用于动态内存分配,比如通过malloc()函数分配内存。

    maps节点

      Linux内核的procfs文件系统为每一个进程维护一个/proc/<pid>/maps节点,用来实时显示该进程的地址映射表。

    #cat /proc/1793/maps
    00010000-003bf000 r-xp 00000000 01:00 295 /usr/bin/test      //读+执行权限,代码段
    003ce000-003d7000 rw-p 003ae000 01:00 295 /usr/bin/test      //读写权限,数据段
    003d7000-003f3000 rw-p 00000000 00:00 0
    0060e000-00f5d000 rw-p 00000000 00:00 0 [heap]               //用户态堆
    f6157000-f686c000 rw-p 00000000 00:00 0
    f6889000-f6ee1000 rw-p 00000000 00:00 0
    f6f62000-f706e000 rw-p 00000000 00:00 0
    f706e000-f7109000 rw-p 00000000 00:00 0
    f711c000-f7122000 r-xp 00000000 01:00 153 /lib/libthread_db-1.0.so
    f7122000-f7131000 ---p 00006000 01:00 153 /lib/libthread_db-1.0.so
    f7131000-f7132000 r--p 00005000 01:00 153 /lib/libthread_db-1.0.so
    f7132000-f7133000 rw-p 00006000 01:00 153 /lib/libthread_db-1.0.so
    f7133000-f7344000 rw-p 00000000 00:00 0
    f7344000-f746d000 r-xp 00000000 01:00 132 /lib/libc-2.24.so
    f746d000-f747d000 ---p 00129000 01:00 132 /lib/libc-2.24.so
    f747d000-f747f000 r--p 00129000 01:00 132 /lib/libc-2.24.so
    f747f000-f7480000 rw-p 0012b000 01:00 132 /lib/libc-2.24.so
    f7480000-f7483000 rw-p 00000000 00:00 0
    f7483000-f7527000 r-xp 00000000 01:00 139 /lib/libm-2.24.so
    f7527000-f7536000 ---p 000a4000 01:00 139 /lib/libm-2.24.so
    f7536000-f7537000 r--p 000a3000 01:00 139 /lib/libm-2.24.so
    f7537000-f7538000 rw-p 000a4000 01:00 139 /lib/libm-2.24.so
    f7538000-f756b000 r-xp 00000000 01:00 456 /usr/lib/libncurses.so.5.9
    f756b000-f757a000 ---p 00033000 01:00 456 /usr/lib/libncurses.so.5.9
    f757a000-f757d000 rw-p 00032000 01:00 456 /usr/lib/libncurses.so.5.9
    f757d000-f757f000 r-xp 00000000 01:00 136 /lib/libdl-2.24.so
    f757f000-f758e000 ---p 00002000 01:00 136 /lib/libdl-2.24.so
    f758e000-f758f000 r--p 00001000 01:00 136 /lib/libdl-2.24.so
    f758f000-f7590000 rw-p 00002000 01:00 136 /lib/libdl-2.24.so
    f7590000-f75b0000 r-xp 00000000 01:00 128 /lib/ld-2.24.so
    f75bb000-f75bf000 rw-p 00000000 00:00 0
    f75bf000-f75c0000 r--p 0001f000 01:00 128 /lib/ld-2.24.so
    f75c0000-f75c1000 rw-p 00020000 01:00 128 /lib/ld-2.24.so
    ff8b0000-ff8d1000 rw-p 00000000 00:00 0   [stack]           //栈
    ffff0000-ffff1000 r-xp 00000000 00:00 0   [vectors]

      显而易见,堆从下往上增长,栈从上往下增长。

      代码段和数据段都在低地址,分开存放是基于安全考虑,因为黑客可能通过篡改数据段从而植入代码。现在的CPU一般都有一个开关,可以配置为不执行数据段代码。

    glibc和ptmalloc

      glibc库使用第三方的ptmalloc库实现用户态堆管理器,ptmalloc(POSIX thread malloc)库增加了多线程支持,为每一个线程分配单独的堆。

      ptmalloc库维护一个全局的结构体变量main_arena(主场地),与main_heap关联。main_arena->top指向最大的空闲块,main_arena->fastbinsY[]维护小块内存的链表集合,main_arena->bins[]维护大块内存的链表集合。

      主进程中的其他线程申请内存时,ptmalloc()库为它创建Non-arena辅场地。

      Main-arena从低地址向高地址增长,Non-arena从高地址向低地址增长。

      主进程使用Main-arena,创建线程时通过new_heap创建新的堆,按需分配,每次分配固定大小,如果线程内存不够,则另外再分配固定大小内存,因此线程的堆是不连续的。

      Main-arena和Non-arena的个数和大小是可以配置的。

      ptmalloc()通过brk()系统调用(brk分配的基本单位是132KB)向内存批发大块内存,通过malloc()零售小块内存。

      应用程序通过malloc()向ptmalloc堆管理器申请小块内存,当单次申请内存大小超过128KB时,ptmalloc会通过brk系统调用向内核申请大块内存。

    堆检测工具

      因为堆管理算法非常复杂,如果出现堆内存泄漏问题,靠人工非常难分析情况,需要借助专门的工具。

      Valgrind工具提供的memcheck功能可以非常高效地检测内存泄漏问题。

      Google的Address Sanitizer(asan)工具也非常实用。

  • 相关阅读:
    【1】Chrome
    Vue
    GitHub版本控制工具入门(一)
    Vue.js 组件笔记
    最全的javascriptt选择题整理
    网站如何实现 在qq中发自己链接时,便自动获取链接标题、图片和部分内容
    js 唤起APP
    密码加密MD5,Bash64
    HTTP和HTTPS的区别及HTTPS加密算法
    计算机网络七层的理解
  • 原文地址:https://www.cnblogs.com/justin-y-lin/p/11264856.html
Copyright © 2020-2023  润新知