今天新来的Manager让我最近给组里的同事分享一下最近我处理memory相关issue的经验。我想也好,干脆总结一下。先来介绍一个很基础但是很有用的工具libumem.so,libumem中的u指的是user space,是相对于kernal space来说的。读过linux的同学都知道linux kernal object内存管理的一个重要的概念就是slab。其实这个概念是solaris首先提出来的。
使用libumem配合mdb在 Solaris上进行内存问题的trouble shooting是非常有效的。但是有效的程度取决于经验,mdb是solaris的调试工具,非常强大,但是比较底层。最近开始读solaris internals这本书,觉得sun官方的mdb user guide不是很好,solaris internals反而更实用。libumem的核心思想就是把slab allocator的概念引入到user space来,使得用户态的程序也能借助kernal space中的多种技术帮助程序员发现定位问题。
简单说一下什么叫slab allocator,一个slab就是一类对象的cache,这个slab管理了多个buffer,其中每个buffer都能存放一个对象实例。当程序要一个新的该类对象的时候,slab就给程序一个已经初始化好的对象,当程序释放对象时,并不真正的释放,而是还给slab。slab最大的好处是效率,而且能够避免内存碎片,当然libumem中的每一个对象(内存对象)都有一些附属信息,方便调试。
在solaris中使用libumem非常方便,不需要重新编译代码,只要在启动程序设定LD_PRELOAD=libumem.so.1,系统就会把所有的malloc/free导向到libumem中,这一点在production中尤为重要,我们不可能要求在production环境中安装gcc或者gdb这样的工具的,或者想象一下你是在客户那里试图解决问题,难道还要带上源代码在客户的机器上编译不成。libumem.so是随着solaris安装的,每一台solaris 9以上版本的机器上都有。
就内存诊断工具我们常用的还有purify,purify也很强大,但是使用它需要重新编译代码。而且purify很慢。而libumem能和mdb配合使用。当然真正碰到问题,我常常是把手头所有的工具都用上-:)
网上比较好的文档有两篇,一是Sun的官方文档http://access1.sun.com/techarticles/libumem.html ,不过这个网址有时候不太稳定。还有一个是Judy的Bloghttp://developers.sun.com.cn/blog/judy/entry/200703091。这里我主要翻译一下Sun的官方文档,再结合一个Judy的文章。我希望我写的这篇文章能起到Hands On的效果,因为常常官方文档太学究,很多根本不是写给初学者看的,或者嘛翻译过来的东西不是出自技术人员之手,看的人中文是看懂了,看完了还是不知道什么意思。
先看这样一段很简单的代码Test.cpp:
int main()
{
int *p = (int*)malloc(50);
p = (int*)malloc(100);
free(p);
pause();
return 0;
}
-bash-3.00$ g++ -o Test Test.cpp //编译Test.cpp
-bash-3.00$ UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1 ./Test &
[1] 5503
UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1是送3个环境变量给Test。具体的解释我先不解释,可以使用 man umem_debug查看。
-bash-3.00$ mdb -p 5503 //使用mdb attach到Test进程,进程ID=5503
Loading modules: [ ld.so.1 libumem.so.1 libc.so.1 ]
>
> ::umalog //使用umalog打印所有的内存分配释放记录
T-0.000000000 addr=80a0f80 umem_alloc_112
libumem.so.1`umem_cache_free_debug+0×135
libumem.so.1`umem_cache_free+0x3f
libumem.so.1`umem_free+0xd5
libumem.so.1`process_free+0xfd
libumem.so.1`free+0×14
main+0×47
_start+0×80
T-0.000003228 addr=80a0f80 umem_alloc_112
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0x15c
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
main+0×36
_start+0×80
T-0.000067020 addr=809cf80 umem_alloc_64
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0x15c
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
main+0×26
_start+0×80
可以看到,Test程序一共有两次内存分配,1次内存释放。log是按照时间顺序的,排在前面的记录是最近的内存操作。umem_alloc_112是cache的名字,umem_alloc_112是专门用来管理size=112字节的内存的。addr=80a0f80表示内存buffer的起始地址。umalog还包含了调用栈,这个信息很有用。其实在实际使用中直接输入::umalog你会头昏的,因为一般有一点规模的程序会有非常多的内存操作,你就像大海捞针,所以我一般会这样:
> ::umalog ! less // ! 的作用就是管道,意思是把umalog的结果输出给less,这样我就可以在mdb中使用less的功能进行查找翻页
在打印log方面还可以这样
> ::umem_log
CPU ADDR BUFADDR TIMESTAMP THREAD
1 0806f0c8 080a0f80 c6bb095680bec 00000001
1 0806f064 080a0f80 c6bb09567ff50 00000001
1 0806f000 0809cf80 c6bb095670620 00000001
这样也打印了内存操作,CPU=1表示代码运行在CPU1上,ADDR其实是LOG的地址,我们先不关心。BUFADDR和::umalog中的addr是吻合的,THREAD表示该内存操作在程序的哪个线程中被调用的,这个信息对于分析多线程并发情况下的内存corruption有用。但是和::umalog一样,这样打印信息实在太多。比如我想知道曾经访问过addr=80a0f80这块buffer的记录有哪些:
> ::umem_log ! grep 80a0f80
1 0806f0c8 080a0f80 c6bb095680bec 00000001
1 0806f064 080a0f80 c6bb09567ff50 00000001
前面说到每一块buffer都有一些meta信息用于管理或者debug。
> 080a0f80::whatis
80a0f80 is 80a0f80+0, bufctl 80a1100 freed from umem_alloc_112
通过whatis,我要求mdb告诉我0x080a0f80所在的buffer是什么样子的buffer,于是mdb告诉我,这块buffer的起始地址是0x080a0f80,管理这块buffer的控制结构所在的地址是0x80a1100,发生在这块buffer上最近的操作是free,用于管理这一类buffer的cache的名字是umem_alloc_112。
> 80a1100::bufctl //已知buffer ctl structure的地址,要求打印bufctl的信息
ADDR BUFADDR TIMESTAMP THRD CALLER
080a1100 080a0f80 c6bb095680bec 1 libumem.so.1`process_free+0xfd
如果要更详细的信息
> 80a1100::bufctl_audit
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
80a1100 80a0f80 c6bb095680bec 1
8090710 806f0c8 0
libumem.so.1`umem_cache_free_debug+0×135
libumem.so.1`umem_cache_free+0x3f
libumem.so.1`umem_free+0xd5
libumem.so.1`process_free+0xfd
libumem.so.1`free+0×14
main+0×47
_start+0×80
可见ADDR=80a1100是bufctl_audit的起始地址,BUFFADDR=80a0f80是buffer的起始地址,CACHE=8090710是cache的起始地址,已知buffer addr可以通过whatis获取bufctl的地址,已知bufctl可以通过bufctl或者bufctl_audit获取buffer的地址。
> 8090710::umem_cache //已知cache的指针,打印cache的数据结构,BUFTOTL=32表示该cache一共管理32块buffer。
ADDR NAME FLAG CFLAG BUFSIZE BUFTOTL
08090710 umem_alloc_112 020f 80000000 112 32
所以我们推断,buffer的meta信息中肯定有指向bufctl的指针,bufctl中一定有指向buffer的指针,bufctl_audit里面一定含有cache的指针,call stack的指针…下一篇文章我来详细的介绍这些数据结构。