編輯 / 芹菜
出品 / 云巔論劍
青囊,喜歡運動T恤加皮褲的非典型程序猿。此時,他正目不轉(zhuǎn)睛注視著屏幕上一行行的代碼,內(nèi)存泄漏這個問題已經(jīng)讓他茶飯不思兩三天了,任憑偌大的雨滴捶打著窗戶也無動于衷。就這么靜悄悄地過了一會兒,突然間,他哼著熟悉的小曲,仿佛一切來的又那么輕松又愜意。
是誰,在撩動我琴弦,那一段被遺忘的時光......初識內(nèi)存泄漏小白的練級之路少不了前輩們的語重心長。從踏上linux內(nèi)核之路開始,專家們就對青囊說——“遇到困難要學會獨立思考”、“最好的學習方式就是帶著問題看代碼”等等。可這次遇到的問題,讓青囊百思不得其解——機器總內(nèi)存有200G,運行800多天,slabUnreclaim占用 2G,且停掉業(yè)務進程,內(nèi)存占用并沒有降低。客戶非得讓青囊給出合理解釋:
1.slab 2G內(nèi)存是否存在泄漏?如果存在泄漏需要找到原因。
2.如不存在泄漏,需要找到這2G的使用者。
客戶的問題很毒辣,是或不是都得給出合理解釋,需要用數(shù)據(jù)說話。帶著這些疑問,青囊開始了漫長的排查之路。正在青囊全身心投入工作的時候,專家走到他的身旁,靜悄悄的腳步并沒有被專心致志的青囊發(fā)現(xiàn)。專家的眼光放在了那一串串的代碼之上,眼里流露出老父親般的慈祥。專家張口一句:“內(nèi)存泄漏,這類問題不難,內(nèi)核自帶kmemleak可以排查。況且200G的內(nèi)存,slab Unreclaim才占用2G,這根本就沒問題嘛。”青囊一下子抓到了救命稻草,眼里泛著希望的光芒。一來這可能不是一個問題,二來也有排查工具kmemleak了。說干就干,青囊對kmemleak原理和使用進行了深入學習,kmemleak的使用總結起來就兩條指令: echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
青囊迫不及待地登陸服務器執(zhí)行命令——#echo scan > /sys/kernel/debug/kmemleak
bash: /sys/kernel/debug/kmemleak: Permission denied
咦,居然提示沒權限?#ls -l /sys/kernel/debug/kmemleak
ls: cannot access /sys/kernel/debug/kmemleak: No such file or directory
查看系統(tǒng)配置,原來線上環(huán)境根本就沒有打開kmemleak。# CONFIG_DEBUG_KMEMLEAK is not set
青囊立馬想到,可以重新編譯內(nèi)核使能kmemleak,再讓客戶來復現(xiàn)問題,但客戶狠狠地甩了一句,“你們的目標不是提供永不停機的計算服務嗎???”此時此刻,青囊燃起的希望又破滅了。青囊自我安慰了一番,接著跑到專家邊上想請教。他一臉沮喪地說著問題背景,專家聽罷,無可奈何地揮了揮手:“你玩斗地主都是直接上來就王炸的嗎?這種問題應該是自己思考解決的”。青囊只好灰頭土臉地回工位。痛定思痛,他憋著口氣,一定要搞懂內(nèi)存管理,搞懂slab內(nèi)存分配。于是,青囊又開始了自己的鉆研之路。
內(nèi)存泄漏升華之路
經(jīng)過系統(tǒng)學習,青囊對內(nèi)存管理有了大致的了解。按照Linux 內(nèi)存分配API的不同,可以把內(nèi)存簡單分為四種類型——- alloc page 內(nèi)存, 直接調(diào)用__get_free_page/alloc_pages等函數(shù)從伙伴系統(tǒng)申請單個或多個連續(xù)的頁面。
- slab 內(nèi)存,使用kmalloc/kmem_cache_alloc 等slab接口申請內(nèi)存。slab 分配器基于伙伴系統(tǒng),提供了小內(nèi)存的分配能力(雖然也兼容大內(nèi)存分配)。slab分配器從伙伴系統(tǒng)"批發(fā)"大內(nèi)存,然后把大內(nèi)存分成許多小塊內(nèi)存,一個小塊內(nèi)存塊稱為object, 最后把object "零售"給其他內(nèi)核組件使用。
- vmalloc內(nèi)存,vmalloc內(nèi)存也是基于伙伴系統(tǒng),實現(xiàn)了線性映射非連續(xù)內(nèi)存的能力,能夠分配更多,更大的內(nèi)存。
- 用戶態(tài)內(nèi)存,主要指anon page 和file cache,最終由內(nèi)核一個個單一的頁面映射而成。
-
內(nèi)存的內(nèi)容不會再改變, 因為沒有進程能訪問到這塊內(nèi)存。
-
slab的object內(nèi)存對象,可能會殘留使用過的全局變量, 函數(shù)名,字符串,指針,特定數(shù)值(垃圾滿地)。
-
內(nèi)存中充斥著大量內(nèi)容相似(相等)的slab object對象。
sysAK——內(nèi)存泄漏無處遁逃
有了slab分配器系統(tǒng)的學習,以及對內(nèi)存泄漏特征的思考,等到再一次登陸機器時,青囊信心滿滿志在必得。想想畢竟有備而來,這次一定讓內(nèi)存泄漏無處可逃。排查過程大致可以分為四步:1. 確認泄漏的slab通過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達到了4億多個,占用內(nèi)存在1.3G左右,泄漏嫌疑最大,那就拿kmalloc-32來開刀了。2. 啟用crash實時分析kmalloc-32的內(nèi)存3.查找kmalloc-32內(nèi)存頁
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)容
這里可以隨機選擇多個page來分析,這里選擇ffff88031e138000來分析。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(..... 可以看到每個object的內(nèi)容大體相似,但并沒有預期的順利,沒有看到函數(shù)名或者變量名,可青囊知道 ffff882f59bade00 這也是個slab內(nèi)存地址,于是進一步打印其內(nèi)容crash> x /20a 0xffff882f59bade000xffff882f59bade00: 0x460656b7 0xffffffff81685b60 0xffff882f59bade10: 0x63ea63ea00000001 0xffff882f59bade180xffff882f59bade20: 0xffff882f59bade18 0x00xffff882f59bade30: 0x0 0xffff882f59bade380xffff882f59bade40: 0xffff882f59bade38 0x7fb27fb2 0xffffffff81685b60明顯是內(nèi)核只讀段的地址。sym 這個地址,原來是inotify_fsnotify_ops全局變量,從而推出泄漏結構體是struct fsnotify_event_private_data,然后結合fsnotify_event_private_data分配和釋放的代碼,在釋放內(nèi)存時存在不正確的判斷邏輯,導致分配的內(nèi)存沒有添加到鏈表,失去釋放的機會,從而導致泄漏。分析到這里,青囊終于確認這是一起內(nèi)存泄漏,而且泄漏的函數(shù)也定位到了,這下算是可以給客戶一個滿意的答案了。客戶問題得到了解決,青囊也對內(nèi)存管理有了深刻的認識,還形成了自己的一套分析方法。但是青囊心里也清楚,泄漏的內(nèi)存不是每次都能找到函數(shù)名或者可視字符,手工使用crash查看的內(nèi)存樣本也不一定夠,還要對內(nèi)存地址比較敏感。于是青囊想把這套分析方法提煉成工具,可以對內(nèi)存泄漏這類問題實施快速一鍵診斷,且不要懂內(nèi)存知識,人人都可以上手分析。抱著這樣的理念,實現(xiàn)了5個核心功能:
-
自動判斷系統(tǒng)是否存在泄漏。
-
自動判斷是slab, vmalloc還是alloc page泄漏。
-
掃描全局內(nèi)存,找到內(nèi)存中slab object最多,且內(nèi)容相似度最高的object。
-
動態(tài)采集內(nèi)存的分配和釋放。
-
計算動態(tài)采集地址的內(nèi)容與存量object的內(nèi)容相似度,但達到一定相似度時,則對動態(tài)地址進行標記。
手握sysAK,青囊也蠢蠢欲動想驗證自己的工具,于是找專家們要來正在處理的“內(nèi)存泄漏問題”。專家們只見青囊輕輕地敲擊了一條指令sysak memleak靜靜等待了200秒后,屏幕輸出了令人為之一振的結果:未釋放內(nèi)存匯總:次數(shù) 標記次數(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] 結果直接顯示bond_vminfo_add函數(shù)存在泄漏,因為它分配的地址與內(nèi)存中的59萬個object高度相似。專家們看到這個結果,半信半疑地回去review代碼——bond_vminfo_add函數(shù)竟然真存在泄漏。大家對青囊投來了詫異的眼神,滿是贊許。此刻的暢快,像極了游戲通關之后的感覺。通過自己的努力,最終順利解決了問題,這個過程,只是說起來都覺得充滿了成就感。這次經(jīng)歷,也鼓舞著青囊繼續(xù)前行。一個程序猿的自我修養(yǎng)——別輕易放過bug 。(完)