zoukankan      html  css  js  c++  java
  • AK47所向披靡,内存泄漏一网打尽

    青囊,喜欢运动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的不同,可以把内存简单分为四种类型——

    1. alloc page 内存, 直接调用__get_free_page/alloc_pages等函数从伙伴系统申请单个或多个连续的页面。
    2. slab 内存,使用kmalloc/kmem_cache_alloc 等slab接口申请内存。slab 分配器基于伙伴系统,提供了小内存的分配能力(虽然也兼容大内存分配)。slab分配器从伙伴系统"批发"大内存,然后把大内存分成许多小块内存,一个小块内存块称为object, 最后把object "零售"给其他内核组件使用
    3. vmalloc内存,vmalloc内存也是基于伙伴系统,实现了线性映射非连续内存的能力,能够分配更多,更大的内存。
    4. 用户态内存,主要指anon page 和file cache,最终由内核一个个单一的页面映射而成

    image

    青囊不知道自己这样的理解到底属于什么程度,于是想借着客户的问题,顺便也动手练习一下。虽然内存的使用进程成千上万,但作为物理存储介质,不管内存被哪个进程使用,物理上都是不可移动的。泄漏的内存是“躺尸”一样的存在,而且特征还非常明显。因为泄漏的slab内存,就好比聚会后的场地,总会留下点什么。

    其特点有——

    1. 内存的内容不会再改变, 因为没有进程能访问到这块内存。

    2. slab的object内存对象,可能会残留使用过的全局变量, 函数名,字符串,指针,特定数值(垃圾满地)。

    3. 内存中充斥着大量内容相似(相等)的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来开刀了。

    1. 启用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个核心功能:

    1. 自动判断系统是否存在泄漏。

    2. 自动判断是slab, vmalloc还是alloc page泄漏。

    3. 扫描全局内存,找到内存中slab object最多,且内容相似度最高的object。

    4. 动态采集内存的分配和释放。

    计算动态采集地址的内容与存量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 。

  • 相关阅读:
    leetcode--Populating Next Right Pointers in Each Node II
    leetcode—Populating Next Right Pointers in Each Node
    Pascal's Triangle II
    leetcode—pascal triangle
    leetcode—triangle
    October 23rd, 2017 Week 43rd Monday
    October 22nd, 2017 Week 43rd Sunday
    October 21st 2017 Week 42nd Saturday
    October 20th 2017 Week 42nd Friday
    October 19th 2017 Week 42nd Thursday
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/14844569.html
Copyright © 2011-2022 走看看