AK47所向披靡,內(nèi)存泄漏一網(wǎng)打盡
編輯 / 芹菜
出品 / 云巔論劍
青囊,喜歡運(yùn)動(dòng)T恤加皮褲的非典型程序猿。此時(shí),他正目不轉(zhuǎn)睛注視著屏幕上一行行的代碼,內(nèi)存泄漏這個(gè)問(wèn)題已經(jīng)讓他茶飯不思兩三天了,任憑偌大的雨滴捶打著窗戶也無(wú)動(dòng)于衷。就這么靜悄悄地過(guò)了一會(huì)兒,突然間,他哼著熟悉的小曲,仿佛一切來(lái)的又那么輕松又愜意。
是誰(shuí),在撩動(dòng)我琴弦,那一段被遺忘的時(shí)光......初識(shí)內(nèi)存泄漏小白的練級(jí)之路少不了前輩們的語(yǔ)重心長(zhǎng)。從踏上linux內(nèi)核之路開(kāi)始,專家們就對(duì)青囊說(shuō)——“遇到困難要學(xué)會(huì)獨(dú)立思考”、“最好的學(xué)習(xí)方式就是帶著問(wèn)題看代碼”等等。可這次遇到的問(wèn)題,讓青囊百思不得其解——機(jī)器總內(nèi)存有200G,運(yùn)行800多天,slabUnreclaim占用 2G,且停掉業(yè)務(wù)進(jìn)程,內(nèi)存占用并沒(méi)有降低。客戶非得讓青囊給出合理解釋:
1.slab 2G內(nèi)存是否存在泄漏?如果存在泄漏需要找到原因。
2.如不存在泄漏,需要找到這2G的使用者。
客戶的問(wèn)題很毒辣,是或不是都得給出合理解釋,需要用數(shù)據(jù)說(shuō)話。帶著這些疑問(wèn),青囊開(kāi)始了漫長(zhǎng)的排查之路。正在青囊全身心投入工作的時(shí)候,專家走到他的身旁,靜悄悄的腳步并沒(méi)有被專心致志的青囊發(fā)現(xiàn)。專家的眼光放在了那一串串的代碼之上,眼里流露出老父親般的慈祥。專家張口一句:“內(nèi)存泄漏,這類問(wèn)題不難,內(nèi)核自帶kmemleak可以排查。況且200G的內(nèi)存,slab Unreclaim才占用2G,這根本就沒(méi)問(wèn)題嘛。”青囊一下子抓到了救命稻草,眼里泛著希望的光芒。一來(lái)這可能不是一個(gè)問(wèn)題,二來(lái)也有排查工具kmemleak了。說(shuō)干就干,青囊對(duì)kmemleak原理和使用進(jìn)行了深入學(xué)習(xí),kmemleak的使用總結(jié)起來(lái)就兩條指令: echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
青囊迫不及待地登陸服務(wù)器執(zhí)行命令——#echo scan > /sys/kernel/debug/kmemleak
bash: /sys/kernel/debug/kmemleak: Permission denied
咦,居然提示沒(méi)權(quán)限?#ls -l /sys/kernel/debug/kmemleak
ls: cannot access /sys/kernel/debug/kmemleak: No such file or directory
查看系統(tǒng)配置,原來(lái)線上環(huán)境根本就沒(méi)有打開(kāi)kmemleak。# CONFIG_DEBUG_KMEMLEAK is not set
青囊立馬想到,可以重新編譯內(nèi)核使能kmemleak,再讓客戶來(lái)復(fù)現(xiàn)問(wèn)題,但客戶狠狠地甩了一句,“你們的目標(biāo)不是提供永不停機(jī)的計(jì)算服務(wù)嗎???”此時(shí)此刻,青囊燃起的希望又破滅了。青囊自我安慰了一番,接著跑到專家邊上想請(qǐng)教。他一臉沮喪地說(shuō)著問(wèn)題背景,專家聽(tīng)罷,無(wú)可奈何地?fù)]了揮手:“你玩斗地主都是直接上來(lái)就王炸的嗎?這種問(wèn)題應(yīng)該是自己思考解決的”。青囊只好灰頭土臉地回工位。痛定思痛,他憋著口氣,一定要搞懂內(nèi)存管理,搞懂slab內(nèi)存分配。于是,青囊又開(kāi)始了自己的鉆研之路。
內(nèi)存泄漏升華之路
經(jīng)過(guò)系統(tǒng)學(xué)習(xí),青囊對(duì)內(nèi)存管理有了大致的了解。按照Linux 內(nèi)存分配API的不同,可以把內(nèi)存簡(jiǎn)單分為四種類型——- alloc page 內(nèi)存, 直接調(diào)用__get_free_page/alloc_pages等函數(shù)從伙伴系統(tǒng)申請(qǐng)單個(gè)或多個(gè)連續(xù)的頁(yè)面。
- slab 內(nèi)存,使用kmalloc/kmem_cache_alloc 等slab接口申請(qǐng)內(nèi)存。slab 分配器基于伙伴系統(tǒng),提供了小內(nèi)存的分配能力(雖然也兼容大內(nèi)存分配)。slab分配器從伙伴系統(tǒng)"批發(fā)"大內(nèi)存,然后把大內(nèi)存分成許多小塊內(nèi)存,一個(gè)小塊內(nèi)存塊稱為object, 最后把object "零售"給其他內(nèi)核組件使用。
- vmalloc內(nèi)存,vmalloc內(nèi)存也是基于伙伴系統(tǒng),實(shí)現(xiàn)了線性映射非連續(xù)內(nèi)存的能力,能夠分配更多,更大的內(nèi)存。
- 用戶態(tài)內(nèi)存,主要指anon page 和file cache,最終由內(nèi)核一個(gè)個(gè)單一的頁(yè)面映射而成。
-
內(nèi)存的內(nèi)容不會(huì)再改變, 因?yàn)闆](méi)有進(jìn)程能訪問(wèn)到這塊內(nèi)存。
-
slab的object內(nèi)存對(duì)象,可能會(huì)殘留使用過(guò)的全局變量, 函數(shù)名,字符串,指針,特定數(shù)值(垃圾滿地)。
-
內(nèi)存中充斥著大量?jī)?nèi)容相似(相等)的slab object對(duì)象。
sysAK——內(nèi)存泄漏無(wú)處遁逃
有了slab分配器系統(tǒng)的學(xué)習(xí),以及對(duì)內(nèi)存泄漏特征的思考,等到再一次登陸機(jī)器時(shí),青囊信心滿滿志在必得。想想畢竟有備而來(lái),這次一定讓內(nèi)存泄漏無(wú)處可逃。排查過(guò)程大致可以分為四步:1. 確認(rèn)泄漏的slab通過(guò)slabtop -s -a ,找到使用objects最多且不可回收的slab
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 419440512 419440512 100% 0.03K 3276879 128 13107516K kmalloc-32 810303 401712 49% 0.10K 20777 39 83108K buffer_head 417600 362397 86% 1.05K 13920 30 445440K ext4_inode_cache 267596 241915 90% 0.57K 9557 28 152912K radix_tree_node 216699 154325 71% 0.19K 10319 21 41276K dentry 155958 37367 23% 0.04K 1529 102 6116K ext4_extent_status 可以看到kmalloc-32的active object達(dá)到了4億多個(gè),占用內(nèi)存在1.3G左右,泄漏嫌疑最大,那就拿kmalloc-32來(lái)開(kāi)刀了。2. 啟用crash實(shí)時(shí)分析kmalloc-32的內(nèi)存3.查找kmalloc-32內(nèi)存頁(yè)
crash> kmem -S kmalloc-32 | tailffffea000c784e00 ffff88031e138000 0 128 111 17ffffea00242d0240 ffff88090b409000 0 128 126 2ffffea00436c5800 ffff8810db160000 0 128 127 1ffffea0018b1df40 ffff88062c77d000 0 128 126 2ffffea005947d1c0 ffff881651f47000 0 128 126 2ffffea004d463080 ffff8813518c2000 0 128 126 2ffffea0030644100 ffff880c19104000 0 128 126 2 4.dump內(nèi)存內(nèi)容
這里可以隨機(jī)選擇多個(gè)page來(lái)分析,這里選擇ffff88031e138000來(lái)分析。crash> rd ffff88031e138000 -Sffff88031e138000: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138010: dead000000200200 ffffffff002856f7 .. ......V(.....-------ffff88031e138020: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138030: dead000000200200 ffffffff00284a2a .. .....*J(.....------ffff88031e138040: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138050: dead000000200200 00303038002856f7 .. ......V(.800.------ffff88031e138060: ffff882f59bade00 dead000000100100 ...Y/...........ffff88031e138070: dead000000200200 ffffffff00284a29 .. .....)J(..... 可以看到每個(gè)object的內(nèi)容大體相似,但并沒(méi)有預(yù)期的順利,沒(méi)有看到函數(shù)名或者變量名,可青囊知道 ffff882f59bade00 這也是個(gè)slab內(nèi)存地址,于是進(jìn)一步打印其內(nèi)容crash> x /20a 0xffff882f59bade000xffff882f59bade00: 0x460656b7 0xffffffff81685b60 0xffff882f59bade10: 0x63ea63ea00000001 0xffff882f59bade180xffff882f59bade20: 0xffff882f59bade18 0x00xffff882f59bade30: 0x0 0xffff882f59bade380xffff882f59bade40: 0xffff882f59bade38 0x7fb27fb2 0xffffffff81685b60明顯是內(nèi)核只讀段的地址。sym 這個(gè)地址,原來(lái)是inotify_fsnotify_ops全局變量,從而推出泄漏結(jié)構(gòu)體是struct fsnotify_event_private_data,然后結(jié)合fsnotify_event_private_data分配和釋放的代碼,在釋放內(nèi)存時(shí)存在不正確的判斷邏輯,導(dǎo)致分配的內(nèi)存沒(méi)有添加到鏈表,失去釋放的機(jī)會(huì),從而導(dǎo)致泄漏。分析到這里,青囊終于確認(rèn)這是一起內(nèi)存泄漏,而且泄漏的函數(shù)也定位到了,這下算是可以給客戶一個(gè)滿意的答案了。客戶問(wèn)題得到了解決,青囊也對(duì)內(nèi)存管理有了深刻的認(rèn)識(shí),還形成了自己的一套分析方法。但是青囊心里也清楚,泄漏的內(nèi)存不是每次都能找到函數(shù)名或者可視字符,手工使用crash查看的內(nèi)存樣本也不一定夠,還要對(duì)內(nèi)存地址比較敏感。于是青囊想把這套分析方法提煉成工具,可以對(duì)內(nèi)存泄漏這類問(wèn)題實(shí)施快速一鍵診斷,且不要懂內(nèi)存知識(shí),人人都可以上手分析。抱著這樣的理念,實(shí)現(xiàn)了5個(gè)核心功能:
-
自動(dòng)判斷系統(tǒng)是否存在泄漏。
-
自動(dòng)判斷是slab, vmalloc還是alloc page泄漏。
-
掃描全局內(nèi)存,找到內(nèi)存中slab object最多,且內(nèi)容相似度最高的object。
-
動(dòng)態(tài)采集內(nèi)存的分配和釋放。
-
計(jì)算動(dòng)態(tài)采集地址的內(nèi)容與存量object的內(nèi)容相似度,但達(dá)到一定相似度時(shí),則對(duì)動(dòng)態(tài)地址進(jìn)行標(biāo)記。
手握sysAK,青囊也蠢蠢欲動(dòng)想驗(yàn)證自己的工具,于是找專家們要來(lái)正在處理的“內(nèi)存泄漏問(wèn)題”。專家們只見(jiàn)青囊輕輕地敲擊了一條指令sysak memleak靜靜等待了200秒后,屏幕輸出了令人為之一振的結(jié)果:未釋放內(nèi)存匯總:次數(shù) 標(biāo)記次數(shù) 函數(shù)66 62 bond_vminfo_add 0x7c/0x200 [bonding]109 0 memleak_max_object 0x3f7/0x7e0 [mem]33 0 inet_bind_bucket_create 0x21/0x701 0 copy_fs_struct 0x22/0xb01 0 tracepoint_add_probe 0xf8/0x430
slab: kmalloc-64 object地址: 0xffff88003605e000 相似object數(shù)量: 593975泄漏函數(shù): bond_vminfo_add 0x7c/0x200 [bonding] 結(jié)果直接顯示bond_vminfo_add函數(shù)存在泄漏,因?yàn)樗峙涞牡刂放c內(nèi)存中的59萬(wàn)個(gè)object高度相似。專家們看到這個(gè)結(jié)果,半信半疑地回去review代碼——bond_vminfo_add函數(shù)竟然真存在泄漏。大家對(duì)青囊投來(lái)了詫異的眼神,滿是贊許。此刻的暢快,像極了游戲通關(guān)之后的感覺(jué)。通過(guò)自己的努力,最終順利解決了問(wèn)題,這個(gè)過(guò)程,只是說(shuō)起來(lái)都覺(jué)得充滿了成就感。這次經(jīng)歷,也鼓舞著青囊繼續(xù)前行。一個(gè)程序猿的自我修養(yǎng)——別輕易放過(guò)bug 。(完)