青囊,喜歡運動T恤加皮褲的非典型程式猿。此時,他正目不轉睛注視着螢幕上一行行的代碼,記憶體洩漏這個問題已經讓他茶飯不思兩三天了,任憑偌大的雨滴捶打着窗戶也無動于衷。就這麼靜悄悄地過了一會兒,突然間,他哼着熟悉的小曲,仿佛一切來的又那麼輕松又惬意。
是誰,在撩動我琴弦,那一段被遺忘的時光......
初識記憶體洩漏
小白的練級之路少不了前輩們的語重心長。從踏上linux核心之路開始,專家們就對青囊說——“遇到困難要學會獨立思考”、“最好的學習方式就是帶着問題看代碼”等等。可這次遇到的問題,讓青囊百思不得其解——機器總記憶體有200G,運作800多天,slabUnreclaim占用 2G,且停掉業務程序,記憶體占用并沒有降低。客戶非得讓青囊給出合了解釋:
1.slab 2G記憶體是否存在洩漏?如果存在洩漏需要找到原因。
2.如不存在洩漏,需要找到這2G的使用者。
客戶的問題很毒辣,是或不是都得給出合了解釋,需要用資料說話。帶着這些疑問,青囊開始了漫長的排查之路。
正在青囊全身心投入工作的時候,專家走到他的身旁,靜悄悄的腳步并沒有被專心緻志的青囊發現。專家的眼光放在了那一串串的代碼之上,眼裡流露出老父親般的慈祥。專家張口一句:“記憶體洩漏,這類問題不難,核心自帶kmemleak可以排查。況且200G的記憶體,slab Unreclaim才占用2G,這根本就沒問題嘛。”青囊一下子抓到了救命稻草,眼裡泛着希望的光芒。
一來這可能不是一個問題,二來也有排查工具kmemleak了。說幹就幹,青囊對kmemleak原理和使用進行了深入學習,kmemleak的使用總結起來就兩條指令:
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
青囊迫不及待地登陸伺服器執行指令——
#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
檢視系統配置,原來線上環境根本就沒有打開kmemleak。
# CONFIG_DEBUG_KMEMLEAK is not set
青囊立馬想到,可以重新編譯核心使能kmemleak,再讓客戶來複現問題,但客戶狠狠地甩了一句,“你們的目标不是提供永不停機的計算服務嗎!?”
此時此刻,青囊燃起的希望又破滅了。

青囊自我安慰了一番,接着跑到專家邊上想請教。他一臉沮喪地說着問題背景,專家聽罷,無可奈何地揮了揮手:“你玩鬥地主都是直接上來就王炸的嗎?這種問題應該是自己思考解決的”。青囊隻好灰頭土臉地回工位。痛定思痛,他憋着口氣,一定要搞懂記憶體管理,搞懂slab記憶體配置設定。
于是,青囊又開始了自己的鑽研之路。
記憶體洩漏升華之路
經過系統學習,青囊對記憶體管理有了大緻的了解。按照Linux 記憶體配置設定API的不同,可以把記憶體簡單分為四種類型——
- alloc page 記憶體, 直接調用__get_free_page/alloc_pages等函數從夥伴系統申請單個或多個連續的頁面。
- slab 記憶體,使用kmalloc/kmem_cache_alloc 等slab接口申請記憶體。slab 配置設定器基于夥伴系統,提供了小記憶體的配置設定能力(雖然也相容大記憶體配置設定)。slab配置設定器從夥伴系統"批發"大記憶體,然後把大記憶體分成許多小塊記憶體,一個小塊記憶體塊稱為object, 最後把object "零售"給其他核心元件使用。
- vmalloc記憶體,vmalloc記憶體也是基于夥伴系統,實作了線性映射非連續記憶體的能力,能夠配置設定更多,更大的記憶體。
- 使用者态記憶體,主要指anon page 和file cache,最終由核心一個個單一的頁面映射而成。
青囊不知道自己這樣的了解到底屬于什麼程度,于是想借着客戶的問題,順便也動手練習一下。雖然記憶體的使用程序成千上萬,但作為實體存儲媒體,不管記憶體被哪個程序使用,實體上都是不可移動的。洩漏的記憶體是“躺屍”一樣的存在,而且特征還非常明顯。因為洩漏的slab記憶體,就好比聚會後的場地,總會留下點什麼。
其特點有——
- 記憶體的内容不會再改變, 因為沒有程序能通路到這塊記憶體。
- slab的object記憶體對象,可能會殘留使用過的全局變量, 函數名,字元串,指針,特定數值(垃圾滿地)。
- 記憶體中充斥着大量内容相似(相等)的slab object對象。
基于以上三個特征,青囊這裡也借助“大資料”思維來進行排查,可以使用crash工具在記憶體中尋找出現最多的object,并且在object中找到可視的函數名,全局變量,再結合代碼,就可以推測洩漏的函數了。
sysAK——記憶體洩漏無處遁逃
有了slab配置設定器系統的學習,以及對記憶體洩漏特征的思考,等到再一次登陸機器時,青囊信心滿滿志在必得。想想畢竟有備而來,這次一定讓記憶體洩漏無處可逃。
排查過程大緻可以分為四步:
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億多個,占用記憶體在1.3G左右,洩漏嫌疑最大,那就拿kmalloc-32來開刀了。2. 啟用crash實時分析kmalloc-32的記憶體3.查找kmalloc-32記憶體頁
crash> kmem -S kmalloc-32 | tail
ffffea000c784e00 ffff88031e138000 0 128 111 17
ffffea00242d0240 ffff88090b409000 0 128 126 2
ffffea00436c5800 ffff8810db160000 0 128 127 1
ffffea0018b1df40 ffff88062c77d000 0 128 126 2
ffffea005947d1c0 ffff881651f47000 0 128 126 2
ffffea004d463080 ffff8813518c2000 0 128 126 2
ffffea0030644100 ffff880c19104000 0 128 126 2
4.dump記憶體内容
這裡可以随機選擇多個page來分析,這裡選擇ffff88031e138000來分析。
crash> rd ffff88031e138000 -S
ffff88031e138000: 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的内容大體相似,但并沒有預期的順利,沒有看到函數名或者變量名,可青囊知道 ffff882f59bade00 這也是個slab記憶體位址,于是進一步列印其内容
crash> x /20a 0xffff882f59bade00
0xffff882f59bade00: 0x460656b7 0xffffffff81685b60
0xffff882f59bade10: 0x63ea63ea00000001 0xffff882f59bade18
0xffff882f59bade20: 0xffff882f59bade18 0x0
0xffff882f59bade30: 0x0 0xffff882f59bade38
0xffff882f59bade40: 0xffff882f59bade38 0x7fb27fb2
0xffffffff81685b60明顯是核心隻讀段的位址。sym 這個位址,原來是inotify_fsnotify_ops全局變量,進而推出洩漏結構體是struct fsnotify_event_private_data,然後結合fsnotify_event_private_data配置設定和釋放的代碼,在釋放記憶體時存在不正确的判斷邏輯,導緻配置設定的記憶體沒有添加到連結清單,失去釋放的機會,進而導緻洩漏。
分析到這裡,青囊終于确認這是一起記憶體洩漏,而且洩漏的函數也定位到了,這下算是可以給客戶一個滿意的答案了。
客戶問題得到了解決,青囊也對記憶體管理有了深刻的認識,還形成了自己的一套分析方法。但是青囊心裡也清楚,洩漏的記憶體不是每次都能找到函數名或者可視字元,手工使用crash檢視的記憶體樣本也不一定夠,還要對記憶體位址比較敏感。于是青囊想把這套分析方法提煉成工具,可以對記憶體洩漏這類問題實施快速一鍵診斷,且不要懂記憶體知識,人人都可以上手分析。抱着這樣的理念,實作了5個核心功能:
- 自動判斷系統是否存在洩漏。
- 自動判斷是slab, vmalloc還是alloc page洩漏。
- 掃描全局記憶體,找到記憶體中slab object最多,且内容相似度最高的object。
- 動态采集記憶體的配置設定和釋放。
- 計算動态采集位址的内容與存量object的内容相似度,但達到一定相似度時,則對動态位址進行标記。
青囊把工具命名為sysAK,寓意像AK47一樣,能夠對系統問題快速定位。随着青囊的學習成長,sysAK後續也會加入更多的功能,實作對作業系統全方位的監控,診斷和修複。
手握sysAK,青囊也蠢蠢欲動想驗證自己的工具,于是找專家們要來正在處理的“記憶體洩漏問題”。
專家們隻見青囊輕輕地敲擊了一條指令sysak memleak靜靜等待了200秒後,螢幕輸出了令人為之一振的結果:
未釋放記憶體彙總:
次數 标記次數 函數
66 62 bond_vminfo_add+0x7c/0x200 [bonding]
109 0 memleak_max_object+0x3f7/0x7e0 [mem]
33 0 inet_bind_bucket_create+0x21/0x70
1 0 copy_fs_struct+0x22/0xb0
1 0 tracepoint_add_probe+0xf8/0x430
slab: kmalloc-64
object位址: 0xffff88003605e000
相似object數量: 593975
洩漏函數: bond_vminfo_add+0x7c/0x200 [bonding]
結果直接顯示bond_vminfo_add函數存在洩漏,因為它配置設定的位址與記憶體中的59萬個object高度相似。專家們看到這個結果,半信半疑地回去review代碼——bond_vminfo_add函數竟然真存在洩漏。大家對青囊投來了詫異的眼神,滿是贊許。
此刻的暢快,像極了遊戲通關之後的感覺。通過自己的努力,最終順利解決了問題,這個過程,隻是說起來都覺得充滿了成就感。這次經曆,也鼓舞着青囊繼續前行。一個程式猿的自我修養——别輕易放過bug 。(完)
加入龍蜥社群
加入微信群:添加社群助理-龍蜥社群小龍(微信:openanolis_assis),備注【龍蜥】拉你入群;加入釘釘群:可掃碼或搜釘釘群号(33311793)。歡迎開發者/使用者加入龍蜥OpenAnolis社群交流,共同推進龍蜥社群的發展,一起打造一個活躍的、健康的開源作業系統生态!
龍蜥社群_小龍 龍蜥社群釘釘群二維碼
關于龍蜥社群
龍蜥社群是由企事業機關、高等院校、科研機關、非營利性組織、個人等按照自願、平等、開源、協作的基礎上組成的非盈利性開源社群。龍蜥社群成立于2020年9月,旨在建構一個開源、中立、開放的Linux上遊發行版社群及創新平台。
短期目标是開發Anolis OS作為CentOS替代版,重新建構一個相容國際Linux主流廠商發行版。中長期目标是探索打造一個面向未來的作業系統,建立統一的開源作業系統生态,孵化創新開源項目,繁榮開源生态。
加入我們,一起打造面向未來的開源作業系統!
Https://openanolis.cn