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)工具也非常实用。
Linux高级调试与优化——用户态堆的更多相关文章
- Linux高级调试与优化——gdb调试命令
番外 2019年7月26日至27日,公司邀请<软件调试>和<格蠹汇编——软件调试案例集锦>两本书的作者张银奎老师进行<Linux高级调试与优化>培训,有幸聆听张老师 ...
- Linux高级调试与优化——内存管理
1.物理地址和虚拟地址 Linux采用页表机制管理内存,32位系统中页大小一般为4KB,物理内存被划分为连续的页,每一个页都有一个唯一的页号. 为了程序的的可移植性,进程往往需要运行在flat mem ...
- Linux高级调试与优化——信号量机制与应用程序崩溃
背景介绍 Linux分为内核态和用户态,用户态通过系统调用(syscall)进入内核态执行. 用户空间的glibc库将Linux内核系统调用封装成GNU C Library库文件(兼容ANSI &am ...
- 42.Linux应用调试-初步制作系统调用(用户态->内核态)
1首先来讲讲应用程序如何实现系统调用(用户态->内核态)? 我们以应用程序的write()函数为例: 1)首先用户态的write()函数会进入glibc库,里面会将write()转换为swi(S ...
- Linux高级调试与优化——进程管理和调度
进程管理 进程和文件是Linux操作系统的两个最基本的抽象. 进程是处于执行期的程序,进程不仅仅局限于一段可执行程序代码,通常还包含其他资源,如打开的文件.挂起的信号.内核内部数据.处理器状态.进程地 ...
- Linux高级调试与优化——Address Sanitizer
Address Sanitizer ASAN最早可以追溯到 LLVM 的 sanitizers项目(https://github.com/google/sanitizers),这个项目包含了Addre ...
- Linux高级调试与优化——同时抓取coredump和maps文件
Linux内核源码 Documentation/sysctl/kernel.txt core_pattern: core_pattern: core_pattern is used to specif ...
- Linux高级调试与优化——内存泄漏实战分析
最近在整理Linux调试方面的文档,正好碰到了一个内存泄漏踩栈的问题,借此机会记录一下分析过程. 首先,发现问题之后,赶紧看一下产生coredump文件没有,果不其然,产生了coredump,果断上g ...
- Linux高级调试与优化——ptrace
ptrace (process trace) #include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_ ...
随机推荐
- Uncaught SyntaxError: Unexpected identifier
$.ajax({ //请求头 type:"POST", contentType:"application/x-www-form-urlencoded", url ...
- Nginx请求限制配置
Nginx请求限制配置 请求限制可以通过两种方式来配置,分别是 连接频率限制和请求频率限制 首先我们要知道什么是http请求和连接,浏览器和服务端首先通过三次握手完成连接,然后发起请求,传输请求参数 ...
- Go语言标准库之fmt.Print
Go语言fmt.Printf使用指南 本文整理了Go语言的标准输出流(fmt.Printf)在打印到屏幕时的格式化输出操作. 在这里按照占位符将被替换的变量类型划分,更方便查询和记忆. General ...
- python中的else语句
python语言和其它语言一样在支持else语句,通常else语句和if语句合用,完成程序的分支选择功能. 例如如下打印学成成绩代码: score = int(input("请输入成绩:&q ...
- 如何自己搭建DNS服务器
DNS服务器是计算机域名系统 (Domain Name System 或Domain Name Service) 的缩写,它是由 解析器和域名服务器组成的.域名服务器是指保存有该网络中所有主机的域名和 ...
- jumpserver部署0.3版本 =====( ̄▽ ̄*)b
jumpserver概述 跳板机概述: 跳板机就是一台服务器,开发或运维人员在维护过程中首先要统一登录到这台服务器,然后再登录到目标设备进行维护和操作: 跳板机缺点:没有实现对运维人员操作行为的控制和 ...
- web性能优化-浏览器工作原理
要彻底了解web性能优化的问题,得搞清楚浏览器的工作原理. 我们需要了解,你在浏览器地址栏中输入url到页面展示的短短几秒中,浏览器究竟做了什么,才能了解到为什么我们口中所说的优化方案能够起到优化作用 ...
- 【Linux学习一】命令行CLI、BASH的基本操作
●操作系统的基本结构 操作系统的基本结构通过Kernel(内核)和Shell(壳)构成.常见的Shell分为GUI和CLI GUI 图形方面的shell ------〉windows .mac osC ...
- 用SWPM导入SAP时因用户Culture及系统Locale引发的问题
[问题]SWPM安装SAP时因用户Culture及系统Locale引发的问题 [现象] ①IE浏览器显示空白(SWPM界面不显示) ②SAP安装时出现“Error occurs when execut ...
- python selenium 笔记
1.安装环境 下载python 3.6.4 第一页把最下面的环境变量勾上.第二页把 all user 勾上 直接安装到下一步 安装完成之后 cmd直接输入python 可以看到版本 2.安装 ...