该文章参考宋宝华老师的内存管理课程,详细可以去听阅码场宋老师的课程。
进程的内存消耗和泄漏
● 进程的VMA。
● 进程内存消耗的4个概念:vss、rss、pss和uss
● page fault的几种可能性,major和minor
● 应用内存泄漏的界定方法
● 应用内存泄漏的检测方法:valgrind和addresssanitizer
1、进程的内存消耗
当我们谈到进程消耗的内存,这时候只看 user space 的,是不看内核空间的内存的。

32位的处理器下,每个进程都有一个0-3G的用户空间,但是它们的内核空间是共享的,只有一个。一般情况下,低端内存是映射到内核空间的。
当我们说一个进程的内存消耗的时候,我们只说用户空间的内存消耗,与 kernel space 没关系。

我们知道每一个进程都有一个 task_struct 来描述,task_struct 里面有一个 mm_struct 来描述进程内存资源的,
mm_struct->pgd 是页表指针,mmap 就是一个 vm_area_struct的链表(list of VMAs ),在一个进程跑起来之后,它是由一段一段的VMA构成的,
每一段VMA就代表了一段虚拟地址空间,用户程序的虚拟地址空间0-3G,当一个进程跑起来的时候,它不是覆盖整个0-3G虚拟地址空间的,而是一段一段的,
代码段(R+X)、数据段(R+W)、栈(R+W)、堆(R+W)等等,每一个都是一段VMA,都有起始地址,结束地址,还有权限。
这里举一个简单的例子。


可以看到 这个进程是有很多个VMA组成的,每一个VMA都有 起始地址,大小,权限。 anon 是匿名段。覆盖到了 0-3G的一个范围。

除了通过 pmap 可以看到进程的每一个段的VMA的分别,可以通过 cat /proc/3692/maps 来看具体的分布情况。

如果想看到更具体的,每一个段的具体分布,可以通过 cat /proc/3692/smaps 来看具体的分布情况。
下面列出了其中一个段的具体的情况,Size 是 VMA的大小,可以远大于RSS

当我们跑一个进程 /bin/gonzo 的时候,它的VMA和各个段的映射关系如下图这样。
注意,当这个进程跑起来的时候,会链接一些动态库,这些动态库的代码段是和其他进程共享的。

2、Page fault 的几种可能性
(检查地址合不合法,检查权限合不合法)

MMU 给 CPU 发出 page fault ,在硬件里面,其实有两个寄存器,分别代表 page fault 发生的 地址 和 原因,这个都是可以在硬件上读出来的。
当你的程序出错了,指针乱飞,有下面4种可能,两种合法的,两种不合法的。
不合法的2种:
- 发生在空白区域。(不合法)。你的指针落到下面的一片空白区域(非VMA)的时候,没有页表映射,linux 内核就会触发page fault的处理程序,并且向应用程序发出 segv信号。
- 发生在代码段。(不合法)。代码段的权限是R+X ,当你在代码段写的时候,内核检查了地址是合法的,但是原因是不合法的,权限不对,page fault + SEGV
注意:两种情况都是挂,但是挂的原因不一样,1是地址不合法,2是权限不合法。
合法的2种:
- 发生在堆区。(合法)。你的指针落到一片 堆区域,VMA的权限是R+W , 但是页表的权限是 R ,当你读的时候没有问题,当你第一次写的时候,就会触发page fault ,但是这时候内核就会对比 页表的权限 和 VMA的权限, VMA里面写的是你预期的权限,有W ,是合法的,这个时候不仅不给应用程序发送SEGV,而且还会给应用程序申请1页内存,把页表的权限改为 R+W , 这个过程可以参考 malloc(100M)的过程。
- 发生在代码段。(合法)。代码段的权限是R+X , 当你在代码段执行的时候,如果发生了 page fault ,没关系,Linux内核 会帮你申请页,然后从硬盘里读出代码段到这个页上(如第一次执行的时候,内存里没有,需要从磁盘读到内存),这个过程伴随IO,开销比较大,称之为 major fault
注意:第1种的情况,不伴随IO的情况,开销小,称之为 minor fault ,这两种 fault 都是合法的。
理解 major fault 和 minor fault 是解决 系统性能 和 优化延迟的 非常重要的一环。
这里举一个例子:希望可以
(1) 掌握 major fault 和 minor fault 的区别
(2) 掌握 清除 cache的方法
(3) 掌握 time 和 \time -v 的用法
下面的例子,可以看到,第二次运行,比第一次运行快了大约4倍!这是什么原因呢?

这里其实是,第一次运行过程,文件已经被 load 进内存,成为 page cache了,但是如何证实这一点呢?
重新清掉 cache ,使用 \time -v 第一次运行如下:

第二次运行如下,可以清楚看到这个时间变快了,而是 Major fault 变成了0 ,就是少了硬盘读写的过程!

3、Linux 的 某一个进程究竟耗费了多少内存?VSS/RSS/PSS/USS
这个问题很复杂,除了上面的 vss ,rss 外,还有 pss 和 uss ,这些都是 Linux 不同于 RTOS 的显著特点之一。Linux 各个进程既要做到隔离,但是隔离中又要实现共享,比如1000个进程都要用到 libc ,libc的代码段显然在内存中只有1份。
下面的一幅图上有3个进程,pid为1044的 bash、pid为1045的 bash和pid为1054的 cat。每个进程透过自己的页表,把虚拟地址空间指向内存条上面的物理地址,每次切换一个进程,即切换一份独特的页表。
仅从此图而言,进程1044的 vss 和 rss 分别是:
vss= 1+2+3
rss= 4+5+6
但是是不是“4+5+6”就是1044这个进程耗费的内存呢?这显然也是不准确的,因为4明显被3个进程指向,5明显被2个进程指向,坏事是大家一起干的,不能1044一个人背黑锅。
这个时候,就衍生出了一个pss(按比例计算的驻留内存, Proportional Set Size )的概念,仅从这一幅图而言,进程1044的pss为:
pss= 4/3 +5/2 +6
最后,还有进程1044独占且驻留的内存 uss(Unique Set Size ),仅从此图而言,
uss = 6
所以,分析Linux,我们不能模棱两可地停留于表面
VSS >= RSS >= PSS >= USS
那我们该如何查看系统里的这些内存概念呢? SMEM

下面这两个命令可以画出 饼状图(pie 基于PSS) 柱状图 bar ,这些做研究,做论文可以用得到。


4、内存泄漏的概念和排查方法
当用户层的内存发生泄漏的时候,我们只需要关心 USS 就好了 。
内存泄漏的概念,不是下面理解的这样,你应用程序申请了100M内存,跑着跑着挂掉了,没来得及free,就造成了内存泄漏。
对Linux这样一个有进程概念的系统而言,当你进程挂掉的时候,你所有的资源都被内核回收了,你的内存都会被释放了,

我们谈到的内存泄漏,不是当你程序死掉的时候泄漏的, 而是进程运行的越久,你进程消耗的内存会越来越多,这就叫 memory leak
比如,你写了一个浏览器,每打开一个页面,你申请 100k 和 4K 的内存,当你关闭这个页面的时候,只释放了100k的内存,当你没日没夜的运行这个浏览器,
打开的页面越多,你内存泄漏的就越多。

实际工程中,我可以采用 连续多点采样法来确认是否有内存泄漏的情况发生,8点 9点 10点 11点 12点 等等,,,
举个例子

下面就证明了 内存泄漏了(应用层的内存泄漏,主要看USS就够了)

当你已经确认没有内存泄漏的情况之下呢,欢迎你使用两个工具。

这个程序会告诉我们,是哪个地方发生了内存泄漏,


gcc5.0 之后,可以用gcc的参数来检测内存泄漏,编译的时候,需要加参数


内存泄漏 可能发生在 用户空间 也可能发生在 内核空间!
用户空间:看 USS
内核空间:关注 slab 和 vmalloc的内存 (meminfo)

slabtop 可以看出 slab的变化情况,就可以看出 哪一个slab的函数,申请和释放不成对,可以方便定位

确认到这个之后,内核里面,有一个选项,可以帮助我们定位,

总结 ,全局分析内存泄漏的方法
1、free 命令 或者 meminfo 先看 空闲内存是不是越来越小
2、smem 先排除是不是哪个 APP有内存泄漏,有没有哪一个 APP随着时间的推移,USS越来越多
3、meminfo 看看 slab 和 vmalloc 有没有内存泄漏
cat /proc/slabinfo 看哪个接口的slab 再增长
4、KMEMLEAK 通过内核来帮助定位
有个案例就是,在驱动里,有个指针,这个指针本身是kmalloc出来的,指针指向的东西,也是kmalloc出来的,但是每次释放的时候,只释放了指向的那个东东,所以内存泄漏的非常缓慢。

cat /proc/slabinfo 最后发现是 kmalloc-8 的slab随着时间的增长越来越大,最后使能了 KMEMLEAK ,把这个问题定位到了。

要经常使用下面的信息,来帮助自己判断问题
/proc/meminfo
/proc/slabinfo
/proc/buddyinfo
/proc/zoneinfo
本文介绍了Linux进程的内存消耗,包括VMA、pagefault的类型、内存泄漏的界定与检测方法。重点讨论了VSS、RSS、PSS和USS的区别,并提供了检查内存泄漏的工具valgrind和addresssanitizer。此外,还讲解了如何通过/proc目录下的文件监控内存状态。

2636

被折叠的 条评论
为什么被折叠?



