一文搞定, 轻松掌握, 进程的内存消耗和泄漏
进程的 VMA
进程内存消耗的 4 个概念: vss、rss、pss 和 uss
page fault 的几种可能性, major 和 minor
应用内存泄漏的界定方法
应用内存泄漏的检测方法:valgrind 和 addresssanitizer
本节重点阐述 Linux 的应用程序究竟消耗了多少内存?
一是,看到的内存消耗,并不是一定是真的消耗。
二是,Linux 存在大量的内存共享的情况。动态链接库的特点:代码段共享内存,数据段写时拷贝。把一个应用程序跑两个进程,这两个进程的代码段也是共享的。
当我们评估进程消耗多少内存时,就是指在用户空间消耗的内存,即虚拟地址在 0~3G 的部分,对应的物理地址内存。内核空间的内存消耗属于内核,系统调用申请了很多内存,这些内存是不属于进程消耗的。
原文连接:https://github.com/0voice/linux_kernel_wiki
进程的虚拟地址空间 VMA
task_struct 里面有个 mm_struct 指针, 它代表进程的内存资源。pgd,代表 页表的地址; mmap 指向 vm_area_struct 链表。 vm_area_struct 的每一段代表进程的一个虚拟地址空间。vma 的每一段,都可能是可执行程序的某个数据段、某个代码段,堆、或栈。一个进程的虚拟地址,是在 0~3G 之间任意分布的。
上图 提供三种方式,看到进程的 VMA 空间。
pmap 3474
基地址,size, 权限,
通过以上的方式,可以看到进程的虚拟地址空间,分布在 0~3G,任意一小段一小段分布的。
应用程序运行起来,就是一堆各种各样的 VMA。VMA 对应着 堆、栈、代码段、数据段、等,不在任何段里的虚拟地址空间,被认为是非法的。
当指针访问地址时,落在一个非法的地址,即不在任何一个 VMA 区域。相当于访问一个非法的地址,这些虚拟地址没有对应的物理地址。应用程序收到 page fault,查看原因,访问非法位置,返回 segv。
在 VMA 的东西,不等于在内存。调 malloc 申请了 100M 内存,立马会多出一个 100M 的 VMA,代表这段 vma 区域有 r+w 权限。
应用程序访问内存,必须落在一个 VMA 里。其次,落在一个 VMA 里也不一定对。把 100M 的堆申请出来,100M 内存页全部映射为 0 页。页表里每一页写的只读,页表和硬件对应,MMU 只查页表。而在页表项中指向物理地址的权限是只读,所以在任何时候,去写其中任何一页,硬件都会发生缺页中断。
Linux 内核在缺页中断的处理程序,通过 MMU 寄存器读出发生 page fault 的地址和原因。发现此时 page fault 的原因是写一个页表里记录只读的物理地址,而 vma 记录的虚拟地址又是 r+w,此时,linux 会申请一页内存。同时把页表中的权限改为 r+w。
总结:Linux 内核通过 VMA 管理进程每一段虚拟地址空间和权限。一旦发生 page fault,如果没有落在任何一个 vma 区域,会干掉。
VMA 的起始地址+size,用来限定程序访问的地址是否合法。VMA 中每一段的权限,是来界定访问这段地址是否使用正确的方式访问。
把所有的 vma 加起来,构成进程的虚拟地址空间,但这并不代表进程真实耗费的内存。拿到之后才是真实耗费的内存,RSS。耗费的虚拟内存,是 VSS。
page fault 的几种可能性
1、申请堆内存 vma,第一次写,页表里的权限是 R ,发生 page fault,linux 会去申请一页内存,此时把页表权限设置为 R+W。
2、内存访问落在空白非法区域,程序收到 segv 段错误。
3、代码段在 VMA 记录是 R+X,此时如果对代码段执行写,程序会收到 segv 段错误。
minor 和 major 缺页
缺页,分为两种情况:主缺页 和次缺页。
主缺页 和次缺页,区别就是 申请内存时,是否需要读硬盘。前者需要。
如上图第 4 种情况,在代码段里执行时,出现缺页。linux 申请一页内存,而且要从硬盘中读取代码段的内容,此时产生了 IO,称为 major 缺页。
无论是代码段还是堆,都是边执行边产生缺页中断,申请实际的内存给代码段,且从硬盘中读取代码段的内容到内存。这个过程时间比较长。
minor: malloc 的内存,产生缺页中断。去申请一页内存,没有产生 IO 的行为。major 缺页处理时间,远大于 minor。
vss、rss、pss 和 uss 的区别
如上图,中间是一根内存条。三个进程分别是 1044,1045,1054, 每一个进程对应一个 page table,页表项记录虚拟地址如何往物理地址转换。硬件里的寄存器,记录页表的物理地址。当 linux 做进程上下文切换时,页表也跟着一起切换。
三个进程都需要使用 libc 的代码段。
VSS = 1 +2 +3
RSS = 4 +5 +6
PSS= 4/3 + 5/2 + 6 比例化的
USS= 6 独占且驻留的
工具:smem ,查看进程使用内存的情况。一般来讲,进程使用的内存量,还是看 PSS,强调公平性。看内存泄漏看 USS 就好了。
内存泄漏 界定和检测方法
界定:连续多点采样法,随着时间越久,进程耗费内存越多。
主要由内存申请和释放不是成对引起。RSS/USS 曲线,
观察方法:使用 smem 工具查看多次进程使用内存,USS 使用量。
检查工具:1、valgrind ,会跑一个虚拟机,运行时检查进程的内存行为。会放慢程序的速度。不需要重新编译程序。2、addressanitizer,需要重新编译程序。编译时加参数,-fsanitizegcc 4.9 才支持,只会放慢程序速度 2~3 倍。
评论