- 論壇徽章:
- 0
|
qiuhan
2007.12.7
現(xiàn)象:
對apache使用LoadRunner進行壓力測試,使用top命令發(fā)現(xiàn)wired內(nèi)存不斷增長,導致
內(nèi)存耗盡。(內(nèi)存大小為512M)
分析:
我們從wired的內(nèi)存的分配開始,自底向上開始分析。
首先,我們對wired內(nèi)存的分配方式進行分類,內(nèi)核中使用cnt.v_wire_count來統(tǒng)計wired頁面數(shù)量,
atomic_add_int(&cnt.v_wire_count, 1);
是增加wired頁面(調(diào)用函數(shù)有vm_page_alloc vm_page_wire)
atomic_subtract_int(&cnt.v_wire_count, 1);
是減少wired頁面(調(diào)用函數(shù)有_pmap_unwire_pte_hold pmap_release vm_page_unwire)
我們覺得vm_page_wire過于泛泛,再對調(diào)用vm_page_wire的函數(shù)進行分類,有
socow_setup, do_sendfile, allocbuf, vm_fault, vm_thread_swapin, vm_page_grab
因此加上vm_page_alloc分成7類,用數(shù)組wired_page_cnt來分別表示每一類的wired頁面數(shù).
在struct vm_page中加入int type;來區(qū)分不同類別,在增加wired頁面時對type進行賦值,
并以type值為下標增加wired_page_cnt數(shù)組中對應的元素;在減少wired頁面時也以type為
下標減少wired_page_cnt數(shù)組中對應的元素。
為了便于查看,我們增加了一個ddb的show wired命令來查看數(shù)組中的元素:
DB_SHOW_COMMAND(wired, vm_page_print_wired_info)
我們用修改后的內(nèi)核進行壓力測試,當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
...
我們發(fā)現(xiàn)show wired顯示值之和剛好為cnt.v_wire_count的值,證明我們的代碼無誤。
另一方面,我們粗略計算一下29987*4k大小剛好差不多為117M
另外,令我們感興趣的是vm_page_alloc的值最大,而且隨著wired內(nèi)存的增加,僅
vm_page_alloc的數(shù)值增加,其它基本不動。從這里來看,我們先前對vm_page_wire
進行的分類是不必要的。下面我們要關注的就是vm_page_alloc
調(diào)用vm_page_alloc的有pmap_pinit,vm_fault等17個函數(shù),我們?nèi)匀话凑障惹暗乃悸罚?br />
分別統(tǒng)計這些函數(shù)對wired頁面的貢獻。我們也增加一個ddb的命令show alloc來查看。
重新用新內(nèi)核進行壓力測試,當wired內(nèi)存為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內(nèi)存增加約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
我們發(fā)現(xiàn)僅有kmem_malloc的值在增加。注意:
1 為了清晰,我們打印時忽略了小于5的項目
2 我們關注的是增長趨勢,而不是大小
3 實際分析時,開始時allocbuf增長最快,但后來基本不增加反而下降了
調(diào)用kmem_malloc僅有page_alloc(我們沒有編譯memguard_alloc),這是一個好消息,
但隨之我們卻發(fā)現(xiàn)一個壞消息:調(diào)用page_alloc的地方會很分散。因為存在:
keg->uk_allocf = page_alloc;
這時,我們才意識到這種分析方法有著明顯的缺陷:
1 調(diào)用的函數(shù)不能太多,而且對于通過指針調(diào)用的方式不太適合
2 每一層的推進都需要比較多的編碼,而且都需要通過增加參數(shù)或者標志位來區(qū)分
就在我們將要陷入絕望的時候,zzy創(chuàng)造性的提出了用db_backtrace來回溯的方法。
我們依據(jù)i386/i386/db_trace.c中的db_trace_self函數(shù)寫了一個函數(shù)db_trace_self1,
它只有一個參數(shù)depth,標識回溯的深度,實現(xiàn)的功能是返回回溯depth次時的調(diào)用地址(就和我們在
bt一樣,只是注釋掉了db_printf,以免打印出信息)
在struct vm_page中增加u_int32_t addr;標識調(diào)用者的地址(函數(shù)入口+偏移),提供一個大小為
0x10000的數(shù)組uma_bts(注意,這里我們不能使用鏈表或者動態(tài)數(shù)組,以免導致循環(huán)的內(nèi)存分配),
元素記錄了addr以及一個統(tǒng)計計數(shù),并以addr的尾4位作為hash值定位元素。
在vm_page_alloc中調(diào)用db_trace_self1得到addr,并將uma_bts中的對應元素的統(tǒng)計計數(shù)加1;
在vm_page_unwire中如果發(fā)現(xiàn)該page是從vm_page_alloc分配的,便取出vm_page中的addr,并
將uma_bts中的對應元素的統(tǒng)計計數(shù)減1.
增加一個ddb的命令show pagebt來查看數(shù)組uma_bts的內(nèi)容。
關于參數(shù)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
我們發(fā)現(xiàn)uma_zalloc之類都是類似封裝的函數(shù),而我們真正感興趣的是從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
這里我們發(fā)現(xiàn)fo_write和mac_labelzone_alloc都增長的比較快,而fo_write位于sys/file.h中,
是一個inline函數(shù),不容易判定是誰調(diào)用的。沒關系,我們把depth提高到9
這時,有三個函數(shù)引起了我們的注意:mac_socket_label_alloc mac_socket_peer_label_alloc 和
mac_inpcb_label_alloc. 隨著內(nèi)存的減少,我們發(fā)現(xiàn)內(nèi)存的減少就是被它們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
我們的調(diào)試工作到這里其實就可以結束了,剩下的是一個推理過程。
先理一下推理的思路: 這3個函數(shù)都是為MAC分配內(nèi)存的,導致內(nèi)存泄漏的原因肯定是沒有調(diào)用相應的釋放內(nèi)存函數(shù)。而且,我們知道
mac label是依附于內(nèi)核對象的,例如進程,創(chuàng)建進程時,會為label分配內(nèi)存; 銷毀進程時,也應該釋放label。
所以我們要找的就是沒有釋放label的地方。
我們以mac_inpcb_label_alloc為例,發(fā)現(xiàn)調(diào)用它的函數(shù)是mac_init_inpcb, 而調(diào)用mac_init_inpcb的是
in_pcballoc, 在同一文件(netinet/in_pcb.c)中我們可以看到in_pcbdetach; 再看看調(diào)用in_pcbdetach
的地方,有7處,根據(jù)我們的測試環(huán)境我們覺得應該考慮的是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);
我們編譯的內(nèi)核使能了INET6,所以調(diào)用的是in6_pcbdetach。
我們比較一下in6_pcbdetach和in_pcbdetach這兩個函數(shù),就會發(fā)現(xiàn)后者含有:
746 #ifdef MAC
747 mac_destroy_inpcb(inp);
748 #endif
而前者沒有,這就是導致內(nèi)存泄漏的地方。
總結一下吧:
1 對于具有統(tǒng)計特性的bug,gdb可能束手無策,ddb反而可以起到意想不到的效果
2 db_backtrace對于多個函數(shù)調(diào)用同一個函數(shù)的函數(shù)調(diào)用關系的統(tǒng)計分析很方便
3 調(diào)試需要清晰的思路和敏銳的嗅覺,敢于推斷問題可能的原因
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 |
|