Linux中maps/smaps的妙用

这篇文章的目的是为了记录之前做过的一个工作:减小server运行时所占的内存。

这个server本来是一个PC上的多媒体server,后来我们把它移植到android机顶盒上,仍然当一个server使用,但是用户量比之前小很多。

原来的server对内存的使用也没有什么顾虑,一般都配备很大的内存,而机顶盒对内存占用要求比较高,因为它本身的内存就很小,还有其他的程序在运行,所以我们就面临着减少server所占用的内存的问题。

也许你会问,这个问题不是很简单吗,干嘛还要拿来长篇大论。

你说的对,这个问题现在看来确实不算大事。只是项目一开始的时候大家比较关注功能的实现,到了后来才觉得有必要把内存占用给减小。

刚拿到这个问题,你会怎么分析呢。看代码???代码太多了,而且是很久很久以前的代码,没有几个人对它很熟悉。

先在网上搜了一下,发现了一本书《嵌入式linux内存与性能详解》,这本书严格来讲是一些工作经验的总结,很感谢作者的无私,所以作者也是我将学习的对象,要和大家分享。

这本书中提到了很多嵌入式上的内存和性能上的优化方法,对我很有启发。

我一开始尝试在malloc和free上加钩子(hook),类似于android bionic里的malloc_debug_leak.c里面采用的方法,我不是怀疑原来的server有严重的内存泄漏(因为这个server已经跑了很多年了),而是怀疑哪个地方有很大的内存分配。但是不幸的是用加钩子的方法并没看出哪个地方有大内存分配。


内存不是new/malloc出来的,那是哪来的呢?


于是被迫看代码,发现程序中创建了很多线程,每个线程会通过mmap来设置线程栈和信号栈(sigaltstack)。把它们屏蔽掉之后,系统就会默认分配相应的栈给每个线程使用,但是效果不明显,内存占用还是很大。我们知道mmap可以分配很大的内存区域。于是我grep mmap,发现就只有线程栈和线程信号栈用到了mmap。 

不过程序中创建的这些匿名内存在地址空间中的具体位置引起了我的兴趣。我们很多时候能只关注进程的虚拟地址空间分布,知道线程的堆是进程范围内共享的,却很少关注线程的栈是如何管理的。 于是查看maps,看到他们是占用了不同于栈的区域,有点类似于动态库的位置(其实动态库通常也是用mmap来加载的)。 

这时我才开始计算每个区域的实际大小。 终于有了重大发现,好几个很大的区域都是匿名的,但又不是通过mmap分配的。 

于是我采用最原始的办法来定位这个内存是由谁分配的。通过在程序中加上dump maps和smaps的方法来不断缩小范围。

dump maps的方法很简单,将/proc/self/maps文件copy到你指定的文件,smaps类似。

然后对比这些maps,看看是在哪个地方之后,这些匿名的内存区域被分配了。


这个原始的办法很奏效,很快就定位到有一个源文件中在分配epoll相关的数据结构时,用的是new,但申请的size很大,有1920k之多,这么大的内存分配,是不会走brk(malloc的底层)的,而是用mmap来分配。所以在maps中才会有那些匿名的区域。

分析一下原因,原来的server的用户访问量很大,而内存通常也配备充足,所以就分配了很大的内存来处理异步事件。

而将server移植到机顶盒之后,目标用户访问量不会太多,即使用浏览器访问,浏览器开的多线程也不会太多,所以将上面申请的size减小到一个合理的值之后,server所占用的内存大幅减小。


看似很简单的一件事情(现在看来确实也不复杂),但是却能起到很好的效果。

所以C/C++的开发,对于底层的理解必须得深,深了后很多事情就是顺理成章的,很简单的了。

这方面的书籍个人比较喜欢的有:《程序员的自我修养》,《深入理解计算机系统》,《深度探索C++对象模型》,其实上面提到的《嵌入式linux内存与性能详解》也绝对值得一看。


另外多说一句,smaps里面的信息比maps要全,更详细一些,它会将每个动态库所占的内存分成共享的,私有的等。

为了看出某个动态库真正占用了多少内存,我们需要解析smaps里面的各个字段,网上有一个perl脚本smem.pl可以分析smaps文件,该脚本用到了cpan包:Linux::Smaps。


你可能感兴趣的:(android,C++,Perl)