免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1926 | 回复: 0
打印 上一主题 下一主题

MAC内存泄漏的调试 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-12-29 14:22 |只看该作者 |倒序浏览
qiuhan
2007.12.7
现象:
对apache使用LoadRunner进行压力测试,使用top命令发现wired内存不断增长,导致
内存耗尽。(内存大小为512M)
分析:
我们从wired的内存的分配开始,自底向上开始分析。
首先,我们对wired内存的分配方式进行分类,内核中使用cnt.v_wire_count来统计wired页面数量,
atomic_add_int(&cnt.v_wire_count, 1);
是增加wired页面(调用函数有vm_page_alloc vm_page_wire)
atomic_subtract_int(&cnt.v_wire_count, 1);
是减少wired页面(调用函数有_pmap_unwire_pte_hold pmap_release vm_page_unwire)
我们觉得vm_page_wire过于泛泛,再对调用vm_page_wire的函数进行分类,有
socow_setup, do_sendfile, allocbuf, vm_fault, vm_thread_swapin, vm_page_grab
因此加上vm_page_alloc分成7类,用数组wired_page_cnt来分别表示每一类的wired页面数.
在struct vm_page中加入int type;来区分不同类别,在增加wired页面时对type进行赋值,
并以type值为下标增加wired_page_cnt数组中对应的元素;在减少wired页面时也以type为
下标减少wired_page_cnt数组中对应的元素。
为了便于查看,我们增加了一个ddb的show wired命令来查看数组中的元素:
DB_SHOW_COMMAND(wired, vm_page_print_wired_info)
我们用修改后的内核进行压力测试,当top显示wired为117M时,进行查看:
db> show wired
socow_setup: 0
do_sendfile: 0
allocbuf: 351
vm_fault: 486
vm_thread_swapin: 0
vm_page_grab: 0
vm_page_alloc: 29150
db> show page
...
cnt.v_wire_count: 29987
...
我们发现show wired显示值之和刚好为cnt.v_wire_count的值,证明我们的代码无误。
另一方面,我们粗略计算一下29987*4k大小刚好差不多为117M
另外,令我们感兴趣的是vm_page_alloc的值最大,而且随着wired内存的增加,仅
vm_page_alloc的数值增加,其它基本不动。从这里来看,我们先前对vm_page_wire
进行的分类是不必要的。下面我们要关注的就是vm_page_alloc
调用vm_page_alloc的有pmap_pinit,vm_fault等17个函数,我们仍然按照先前的思路,
分别统计这些函数对wired页面的贡献。我们也增加一个ddb的命令show alloc来查看。
重新用新内核进行压力测试,当wired内存为128M时:
db> show alloc
pmap_pinit: 196
_pmap_allocpte: 975
pmap_growkernel: 61
allocbuf: 14932
obj_alloc: 2163
kmem_malloc: 13165
vm_page_grab: 520
total wired: 32013
等待wired内存增加约1M(使用top):
db> show alloc
pmap_pinit: 196
_pmap_allocpte: 975
pmap_growkernel: 61
allocbuf: 14932
obj_alloc: 2163
kmem_malloc: 13560
vm_page_grab: 520
total wired: 32408
我们发现仅有kmem_malloc的值在增加。注意:
1 为了清晰,我们打印时忽略了小于5的项目
2 我们关注的是增长趋势,而不是大小
3 实际分析时,开始时allocbuf增长最快,但后来基本不增加反而下降了
调用kmem_malloc仅有page_alloc(我们没有编译memguard_alloc),这是一个好消息,
但随之我们却发现一个坏消息:调用page_alloc的地方会很分散。因为存在:
keg->uk_allocf = page_alloc;
这时,我们才意识到这种分析方法有着明显的缺陷:
1 调用的函数不能太多,而且对于通过指针调用的方式不太适合
2 每一层的推进都需要比较多的编码,而且都需要通过增加参数或者标志位来区分
就在我们将要陷入绝望的时候,zzy创造性的提出了用db_backtrace来回溯的方法。
我们依据i386/i386/db_trace.c中的db_trace_self函数写了一个函数db_trace_self1,
它只有一个参数depth,标识回溯的深度,实现的功能是返回回溯depth次时的调用地址(就和我们在
bt一样,只是注释掉了db_printf,以免打印出信息)
在struct vm_page中增加u_int32_t addr;标识调用者的地址(函数入口+偏移),提供一个大小为
0x10000的数组uma_bts(注意,这里我们不能使用链表或者动态数组,以免导致循环的内存分配),
元素记录了addr以及一个统计计数,并以addr的尾4位作为hash值定位元素。
在vm_page_alloc中调用db_trace_self1得到addr,并将uma_bts中的对应元素的统计计数加1;
在vm_page_unwire中如果发现该page是从vm_page_alloc分配的,便取出vm_page中的addr,并
将uma_bts中的对应元素的统计计数减1.
增加一个ddb的命令show pagebt来查看数组uma_bts的内容。
关于参数depth的选取,我们在ddb中对vm_page_alloc设置一个断点:
db> break vm_page_alloc
db> c
[thread pid 39 tid 100034 ]
Breakpoint at   vm_page_alloc:  pushl   %ebp
db> bt
Tracing pid 39 tid 100034 td 0xc22e2600
vm_page_alloc(c14610a8,1000,101) at vm_page_alloc
page_alloc(c1442000,1000,d42d9933,101) at page_alloc+0x4c
slab_zalloc(c1442000,101) at slab_zalloc+0xbe
uma_zone_slab(c1442000,1) at uma_zone_slab+0x164
uma_zalloc_bucket(c1442000,1) at uma_zalloc_bucket+0x121
uma_zalloc_arg(c1442000,0,1) at uma_zalloc_arg+0x36c
uma_zalloc(c1442000,1) at uma_zalloc+0x10
mac_labelzone_alloc(1) at mac_labelzone_alloc+0x17
mac_inpcb_label_alloc(1) at mac_inpcb_label_alloc+0xe
mac_init_inpcb(c4639bf4,1) at mac_init_inpcb+0x12
in_pcballoc(c2ac6ac0,c0af6e20,c09c7b88) at in_pcballoc+0x71
tcp_attach(c2ac6ac0) at tcp_attach+0x70
我们发现uma_zalloc之类都是类似封装的函数,而我们真正感兴趣的是从mac_labelzone_alloc
开始的,所以我们把depth设置为8
db> show pagebt
avtab_insertf   0xc0c5501b      745
fo_write        0xc06e7021      13991
vm_map_entry_create     0xc08c118c      617
uma_zalloc      0xc06c71b8      520
pmap_insert_entry       0xc094d38f      2158
mac_labelzone_alloc     0xc087146f      2527
fork    0xc069f5ee      928
scopen  0xc0929646      408
malloc  0xc06ae812      1352
vmspace_alloc   0xc08c0a50      210
vm_object_allocate      0xc08c7a89      131
uma_zalloc_bucket       0xc08b8e83      131
begin   0xc0448ea5      186
VOP_CACHEDLOOKUP        0xc0716f1e      176
Max: fo_write   0xc06e7021      13991
这里我们发现fo_write和mac_labelzone_alloc都增长的比较快,而fo_write位于sys/file.h中,
是一个inline函数,不容易判定是谁调用的。没关系,我们把depth提高到9
这时,有三个函数引起了我们的注意:mac_socket_label_alloc mac_socket_peer_label_alloc 和
mac_inpcb_label_alloc. 随着内存的减少,我们发现内存的减少就是被它们3个给分享了。
db> show pagebt
sigacts_alloc   0xc06c113e      226
vmspace_fork    0xc08c417e      180
mac_socket_label_alloc  0xc0876186      5300
thread_alloc    0xc06c71d5      500
vm_object_shadow        0xc08c92be      119
mac_socket_peer_label_alloc     0xc087630a      5786
vmspace_fork    0xc08c43e0      203
mac_inpcb_label_alloc   0xc087060a      7252
pmap_copy       0xc094e7fe      2103
giant_open      0xc068b911      384
vm_map_insert   0xc08c193c      392
uma_zalloc_arg  0xc08b89a0      148
syscall 0xc09529d6      885
begin   0xc0448ea5      243
avtab_read_item 0xc0c54eaf      745
kobj_class_compile      0xc06d8f23      124
dofilewrite     0xc06e6f42      14793
Max: dofilewrite        0xc06e6f42      14793
我们的调试工作到这里其实就可以结束了,剩下的是一个推理过程。
先理一下推理的思路: 这3个函数都是为MAC分配内存的,导致内存泄漏的原因肯定是没有调用相应的释放内存函数。而且,我们知道
mac label是依附于内核对象的,例如进程,创建进程时,会为label分配内存; 销毁进程时,也应该释放label。
所以我们要找的就是没有释放label的地方。
我们以mac_inpcb_label_alloc为例,发现调用它的函数是mac_init_inpcb, 而调用mac_init_inpcb的是
in_pcballoc, 在同一文件(netinet/in_pcb.c)中我们可以看到in_pcbdetach; 再看看调用in_pcbdetach
的地方,有7处,根据我们的测试环境我们觉得应该考虑的是tcp_close。
tcp_close中有段代码如下:
776 #ifdef INET6
777     if (INP_CHECK_SOCKAF(so, AF_INET6))
778         in6_pcbdetach(inp);
779     else
780 #endif
781         in_pcbdetach(inp);
我们编译的内核使能了INET6,所以调用的是in6_pcbdetach。
我们比较一下in6_pcbdetach和in_pcbdetach这两个函数,就会发现后者含有:
746 #ifdef MAC
747     mac_destroy_inpcb(inp);
748 #endif
而前者没有,这就是导致内存泄漏的地方。
总结一下吧:
1 对于具有统计特性的bug,gdb可能束手无策,ddb反而可以起到意想不到的效果
2 db_backtrace对于多个函数调用同一个函数的函数调用关系的统计分析很方便
3 调试需要清晰的思路和敏锐的嗅觉,敢于推断问题可能的原因
This bug has been reported and accepted by Robert Watson.
http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/netinet6/in6_pcb.c
http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/security/mac/mac_posix_sem.c
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/41770/showart_452936.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP