AI智能
改变未来

Linux裁剪应用内存的一次实践


背景

在嵌入式设备,总会出现内存不够用的情况。我就遇到一个场景,需要把某个应用塞到资源有限的 Linux 设备中。这应用进程所需要的内存已然超过了 Linux 设备所能提供的空闲内存。

在我的场景中,应用跑起来后共享内存占所有物理内存的 70% 了。与其死抠应用代码,少申请内存,还不如想办法转用其他更少资源的共享库。

所以本文并不会涉及到应用实现上如何更少的内存使用,更多是分享如何从 Linux 系统角度分析应用内存的分布和使用情况。

对我而言,则是又一次理论指导实践的过程,嗯,理论落地的感觉真好。

出于保密的需要,本文一些数据和日志等都会用无意义字符替代,但不会影响同学们阅读和理解

内存的基本知识

在 Linux 上,应用使用的内存可以分为两类,分别是 虚拟内存 和 物理内存。应用可以肆意申请内存,在不超过寻址范围的情况下,很少会有申请失败的情况,毕竟对 Linux 来说并没有分配物理内存。直至你真的要操作申请的内存了,Linux 才真切分配物理内存。关于虚拟内存和物理内存的概念本文不阐述,我只是为了说明,分析内存占用,不能看虚拟内存,必须看物理内存。

由于在 Linux 上,有一些内存是共享的,主要集中在应用加载的动态库上。就好像最常用的 C 库,应用打印个 “helloworld” 可都要链接到 C 库。每一个应用都加载一份 C 库多浪费啊,杜绝浪费,从你我做起。于是 Linux 就只会加载一次 C 库,然后映射到不同应用的虚拟地址,实现不同应用(进程)之间“共享”一份动态库。这不是完全意义上的共享,毕竟动态库里的全局变量对每个进程而言都是私有的。正因为共享动态库的存在,我们统计应用(进程)使用的物理内存时,是否包含以及如何包含共享的动态库占用的物理内存,就产生了2个新的概念:PSSUSS

准确来说,跟 PSSUSS 相似的词汇还有 VSSRSS。网上有不少资料,我找到这么一篇文章介绍非常生动:《linux中top命令 VSS,RSS,PSS,USS 四个内存字段的解读。》,不懂的同学自己看资料哈,我这里做个简单的描述:

  • VSS:Virtual Set Size

    就是虚拟内存,包括进程已经申请,虽然不一定分配了物理内存,但进程访问不会触发段错误的内存。

  • RSS:Resident Set Size

    就是所有的物理内存,会统计上用到的动态库的所有内存,即使这动态库多个进程共享。

  • PSS:Proportional Set Size

    也是物理内存,只不过所有动态库的内存是按比例计算,例如 N 个进程链接动态库,那么每个进程只会计算占用动态库 1/N 的空间。

  • USS:Unique Set Size

    也是物理内存,但不包含动态库的内存,也就是进程私有的内存。

查看进程内存使用情况

如果要查看系统内存使用情况,常用

free

命令。但在这里,我们需要精细到进程为单位的内存使用情况。补充一点,所有线程共用一个

mm_struct

实例,因此内存使用情况以进程为单位,无法再细分到线程。

那么如何查看进程的内存使用情况呢?在 PC 上我们可以使用

top

,或者

ps

,例如:

$ toptop - xx:xx:xx up xxx days, xx:xx, x users,  load average: 1.31, 1.50, 2.52Tasks: xxx total,   x running, xxx sleeping,  xx stopped,   x zombie%Cpu(s):  1.4 us,  0.4 sy,  0.0 ni, 98.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 stKiB Mem : xxxx total,  xxxx free, xxxx used, xxxx buff/cacheKiB Swap:   xxxx total,   xxxx free,        0 used. xxxx avail MemPID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND35370 xxxxx     20   0 xxxxxxx56cxxxxxx  xxxxx S   3.9  0.3   1:38.22 xxx39464 xxxxx     20   0   xxxxx  xxxxx   xxxx S   1.6  0.0   0:00.64 xxx....

上面的 VIRT 就是虚拟内存使用情况,等效于 VSS;RES 是物理内存,等效于 RSS;SHR 是共享内存。

但在大多数嵌入式设备上,

top

或者

ps

都是阉割版本的,显示的内容不全,非常无奈,例如我在 openWRT 上执行

ps

,只会显示虚拟内存:

# psroot@xxxx:/# ps wPID USER       VSZ STAT COMMAND1 root      3028 S    /sbin/procd2 root         0 SW   [kthreadd]....

除了使用第三方的

htop

之外,在一个连

ps/top

都没有的非常极端的环境,我们可以直接查询

procfs

,简单粗暴。如何做呢?

cat /proc/<pid>/statm

实际上,从

htop

的源码发现,其也是从 statm 获取的进程内存使用情况。例如,我要查看进程 1 的内存使用情况:

$ cat /proc/1/statm57 502 363 14 0 181 0

这几个数据什么意思呢?查看 Linux 的 Documentation,有如下的注释:

$ cat filesystems/proc.txt...Table 1-3: Contents of the statm files (as of 2.6.8-rc3).........................................................ad8.....................Field    Contentsize     total program size (pages)        (same as VmSize in status)resident size of memory portions (pages)   (same as VmRSS in status)shared   number of pages that are shared   (i.e. backed by a file, sameas RssFile+RssShmem in status)trs      number of pages that are \'code\'   (not including libs; broken,includes data segment)lrs      number of pages of library        (always 0 on 2.6)drs      number of pages of data/stack     (including libs; broken,includes library text)dt       number of dirty pages         (always 0 on 2.6)...

翻译过来就是:

第一个字段:Size : 任务虚拟地址空间的大小(单位:pages)第二个字段:Resident:应用程序正在使用的物理内存的大小(单位:pages)第三个字段:Shared: 共享页数(单位:pages)第四个字段:Trs:程序所拥有的可执行虚拟内存的大小(单位:pages)第五个字段:Lrs:被映像到任务的虚拟内存空间的库的大小(单位:pages)第六个字段:Drs:程序数据段和用户态的栈的大小(单位:pages)第七个字段:dt:脏页的数量

一般情况下,pages 的大小是 4k ,因此上述的结果除以 4 得到的值单位是 KB。在上例中:

$ cat /proc/1/statm57 502 363 14 0 181 0# 表示# 57: 虚拟内存:57/4 = 14 KB# 502:物理内存(RSS): 502/4 = 125 KB# ...

查看进程内存的分布

只是知道进程内存的分布还不能满足我场景的需求。在我的案例中,70%的物理内存都是共享内存,我还需要细致到用了什么库,每个库用了多少物理内存,我才知道该优化什么库。

要查看虚拟内存的分布情况,可以

cat /proc/<pid>/maps

,例如:

# cat /proc/1/maps00400000-0040e000 r-xp 00000000 b3:05 628                                /sbin/procd0041e000-0041f000 r--p 0000e000 b3:05 628                                /sbin/procd0041f000-00420000 rw-p 0000f000 b3:05 628                                /sbin/procd00420000-00422000 rw-p 00000000 00:00 02e7dd000-2e853000 rw-p 00000000 00:00 0                                  [heap]7fa86f9000-7fa881c000 r-xp 00000000 b3:05 445                            /lib/libc-2.23.so7fa881c000-7fa882b000 ---p 00123000 b3:05 445                            /lib/libc-2.23.so7fa882b000-7fa882f000 r--p 00122000 b3:05 445                            /lib/libc-2.23.so7fa882f000-7fa8831000 rw-p 00126000 b3:05 445                            /lib/libc-2.23.s1dd1o...

但还不够,这只知道了每个库隐射到哪段虚拟内存,我还需要知道实际的物理内存。此时需要

cat /proc/<pid>/smaps

smaps

在我的板子上找不到,在内核中检索代码实现,发现需要在内核中使能

CONFIG_PROC_PAGE_MONITOR

。使能后重新编译内核,我们就可以获取实际的物理内存啦。例如:

# cat /proc/1/smaps...7fa8908000-7fa890e000 r-xp 00000000 b3:05 485                            /lib/librt-2.23.soSize:                 24 kBKernelPageSize:        4 kBMMUPageSize:           4 kBRss:                  24 kBPss:                   6 kBShared_Clean:         24 kBShared_Dirty:          0 kBPrivate_Clean:         0 kBPrivate_Dirty:         0 kBReferenced:           24 kBAnonymous:             0 kBAnonHugePages:         0 kBShmemPmdMapped:        0 kBShared_Hugetlb:        0 kBPrivate_Hugetlb:       0 kBSwap:                  0 kBSwapPss:               0 kBLocked:                0 kBVmFlags: rd ex mr mw me...

上例子中,librt 的库有映射到虚拟内存

7fa8908000-7fa890e000

,权限是

r-xp

,虚拟内存是 24kB,RSS 是 24KB,PSS 是 6KB。

数据分析

通过以下的命令可以初步过滤 smap 的 RSS 和 PSS,再根据 PSS 排序,取 PSS 使用量最大的前20个:

cat /proc/<pid>/smaps | awk \'{if ($1 ~ /^[[:digit:]]+/)printf \"<%s> : \", $6if ($1 ~ /^Rss/)printf \"RSS = %sKB; \", $2;if ($1 ~ /^Rss/)printf \"PSS = %sKB\\n\", $2;}\' | sort -hrk 8 | head -n 20

结果如下:

...</usr/lib/libXXXXXXXXX.so> : RSS = 1544KB; PSS = 1544KB</usr/lib/libXXXXXXXX.so> : RSS = 1172KB; PSS = 1172KB</usr/lib/libXXXXXX.so> : RSS = 1148KB; PSS = 1148KB...</lib/libXXXX.so> : RSS = 1072KB; PSS = 1072KB</lib/libXXXX.so> : RSS = 1060KB; PSS = 1060KB</usr/lib/libXXXX.so.30.23.0> : RSS = 824KB; PSS = 824KB<[heap]> : RSS = 692KB; PSS = 692KB</usr/lib/libXXXX.so> : RSS = 664KB; PSS = 664KB</usr/lib/libXXXXX.so.2.0.0> : RSS = 640KB; PSS = 640KB...

由于保密的需要,我就不完整贴出来了。根据统计的结果,PPS 使用量前 20 的动态库竟然占了进程共享内存的90%的物理内存,非常夸张。

采用一些相同功能,却更小巧的动态库还是有很大效果的。此外,通过这个方法也可以知道堆和栈对内存使用的情况,在做应用内存优化的时候也可以有个侧重方向。

优化还在持续进行中,结果就不贴出来了。

总结

本文介绍了 Linux 内存的一些基本理论知识,重点还是在于如何把理论付诸实践。通过这次的应用内存裁剪的分析过程,重温了 VSS、RSS、USS、PSS,以及学习了如何在 Linux 上查询进程的内存使用情况,如何查询虚拟内存的分布,以及进程各个动态库、堆、栈的物理内存使用情况对应用内存裁剪的指导意义。

对大多数人来说,通过

top/ps

查看物理内存就到底了吧。平时注意理论积累,在关键的时候才不会像无头苍蝇。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Linux裁剪应用内存的一次实践