一个疑似slab泄漏问题排查

本文代码基于linux内核4.19.195.
这周同事反馈一个疑似slab内存泄漏的问题,问题是这样的。
同事在做业务升级测试,不断的升级24小时后发现,通过/proc/meminfo看到的slab内存占用比不跑升级测试的机器多了3.5G(整个系统只有8G内存),怀疑是slab内存泄漏了。
找他要了一台不跑升级的机器和一台跑升级测试的机器对比着看,有如下发现:

  1. 通过free命令查看,available确实少了2.6G
  2. 通过/proc/meminfo对比查看,确实是slab相关的几行有着较大的变化,其他条目变化不大
  3. 使用slabtop -s -c收集一下数据,好家伙,第一名和第二名都是和文件相关的radix_tree_node和filp,分别占用了0.45G和0.27G,接下来3-7位分别是kmalloc-1024、ext4_inode_cache、buffer_head、vm_area_struct、task_struct,这几个都是占用了0.2+G内存;另外,注意到一个细节,这几个object的USE项都是0%,what?

先和同事确认业务升级在做的操作,会有fork动作以及使用share memory的动作。
先不管那些细节,顺着同事的思路,如果radix_tree_node或者filp有泄漏,那么我猜测有如下几种可能:

  1. fork之后又重复打开了父进程已经打开了的文件,导致fd越开越多(从而filp越来越大)
  2. 某些文件约写约大,导致radix_tree_node占用内存越来越多
  3. 某些文件已经被删除了,但是因为引用计数的原因导致删除动作迟迟没有进行?

首先,调用

lsof -n | grep deleted

了一把,发现只有11个文件,每个只有5M大小,55M应该不会导致什么问题吧;
然后,通过

for dir in `ls -d /proc/[0-9]*`;do num=`ls $dir/fd/ | wc -l`;comm=`cat $dir/comm`;echo "$comm -> $num" ;done

看看是不是有哪个进程打开了过多的文件,发现最多的一个进程打开了820个fd,其余都是100以下的。进入业务进程查看,发现只打开了42个fd。
再者,进入业务进程的/proc/pid/fd目录下,执行

for file in `ls -l | awk '{print $NF}'`;do du -h $file;done

发现业务进程打开的最大的文件是84M。
获取了这么多数据,好像没什么异常的,那么是如何引发的slab内存泄漏呢?
回想起以前看过的slab内存泄漏的问题,现象与这个链接中是非常相似的,也就是某个slab对象占用的内存非常大(链接中占用的内存80+G),大到和其他对象有着明显的差距。
重新看了slabtop的输出,再次注意到头几个object的USE项都是0%这个细节,回想了一下slab的原理,突然有了一种想法:

slab内存不足时,会从buddy中申请内存,但是这些内存并不是把所有slab object归还给slab时,就会归还的buddy的,slub会保留一部分内存作为缓存,供下次申请时使用。如果业务升级流程将相关slub的缓存给打满,那么就会出现同事反馈的meminfo中显示slab占用内存增加的现象,而且,slabtop中USE是0%(当然这个数值感觉不怎么准)也能得到很好的解释

刚好借此机会,复习一下slab中和这个缓存相关的代码:

struct kmem_cache {
    ****
    unsigned long min_partial; //代表的是理论上kmem_cache在每个node节点上最多拥有的slab个数,若超过这个数量会尝试释放相关page给buddy
    #ifdef CONFIG_SLUB_CPU_PARTIAL
    /* Number of per cpu partial objects to keep around */
    unsigned int cpu_partial; //每个CPU节点上最多拥有的slab个数,若超过这个数量会尝试将相关page释放到node节点上
    #endif
    ****
}

在mm/slub.c的代码中,slab的object释放时会对进行kmem_cache中的阈值进行判断,从而确定slab是转移到node节点上还是还给buddy,下面罗列与归还到buddy相关的一小部分代码

static void deactivate_slab(struct kmem_cache *s, struct page *page,
                void *freelist)
{   
    ****
    if (!new.inuse && n->nr_partial >= s->min_partial)
        m = M_FREE;
    ***
    }else if (m == M_FREE) {
        stat(s, DEACTIVATE_EMPTY);
        discard_slab(s, page);
        stat(s, FREE_SLAB);
    }
}

static void __unfreeze_partials(struct kmem_cache *s, struct page *partial_page)
{
    ****
    if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
        page->next = discard_page;
        discard_page = page;
    }
    ******
}

从代码中可以看到,必须是slab的数量超过了阈值,才会把内存归还给buddy,否则相关内存还是处在slab的管辖中。
理论上内存被slab缓存在自己的object中这个猜想是说的通了,那么如何验证呢?想了下有几种方法:

  1. 继续跑升级测试,看看再过一段时间这个slab占用是否持续增加或者比较平稳了,如果比较平稳了那么猜想正确,否则内核代码可能存在slab内存泄漏问题
  2. 既然同事觉得slab占用内存太大,那么找几个重点用户,shrink一下,看看slab内存是不是能够降回原来的水准,如果可以,也能怎么猜想正确
  3. 把slab缓存的阈值调小,也就是不让slab缓存太多内存,这样slab如果增加的值变小,也是能够怎么猜想正确的

说到这,那么怎么去shrink slab,又怎么去调slab缓存数量的阈值呢?这个问题就交给内核文档去解释吧

What:       /sys/kernel/slab/cache/shrink
Date:       May 2007
KernelVersion:  2.6.22
Contact:    Pekka Enberg <penberg@cs.helsinki.fi>,
        Christoph Lameter <cl@linux-foundation.org>
Description:
        The shrink file is used to reclaim unused slab cache
        memory from a cache.  Empty per-cpu or partial slabs
        are freed and the partial list is sorted so the slabs
        with the fewest available objects are used first.
        It only accepts a value of "1" on write for shrinking
        the cache. Other input values are considered invalid.
        Shrinking slab caches might be expensive and can
        adversely impact other running applications.  So it
        should be used with care.
 
What:       /sys/kernel/slab/cache/min_partial
Date:       February 2009
KernelVersion:  2.6.30
Contact:    Pekka Enberg <penberg@cs.helsinki.fi>,
        David Rientjes <rientjes@google.com>
Description:
        The min_partial file specifies how many empty slabs shall
        remain on a node's partial list to avoid the overhead of
        allocating new slabs.  Such slabs may be reclaimed by utilizing
        the shrink file.

还有一个/sys/kernel/slab/cache/cpu_partial,在文档里找不到,不知道为啥。
顺带提一下,shrink的代码挺有意思,执行shrink的函数是__kmem_cache_shrink()。另外,可以通过观察/proc/PID/status中VmRSS的值来判断是否存在内存泄漏
除此之外,笔者还发现,在4.19版本的内核中(4.14也有该问题),在使能了kmem的memory cgroup中,会为每个memory cgroup分配一个kmem_cache结构体实例,用于该cgroup对应slub的内存分配,具体可以查看函数slab_pre_alloc_hook()->memcg_kmem_get_cache()。也就是说,每个cgroup会拥有属于自己的一个slub管理结构体,各个cgroup之间的内存分配并不是在一个kmem_cache结构体实例中处理的,这样可能带来的后果是,cgroup1分配了1个8byte的内存,cgroup2也分配了1个8byte的内存,在系统总体来看,系统中实际的内存支出是2个page(假设这个kmem_cache每次从buddy系统中获取的内存是一个page),与同在一个cgroup里面申请两个8byte内存,系统中实际的内存支出1个page是不同的。这样带来的后果是,若系统中存在的memory cgroup数量比较多的话,会带来较大的内存消耗,笔者曾经遇到过,因为用户态没有删除memory cgroup,导致的系统slab占用是正常占用的3-4倍的问题。这个问题在5.4内核上得到了解决,也就是所有cgroup中的slub内存分配都使用全局的kmem_cache。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当我们说“slab占用过高”时,通常指的是在Linux系统中,slab内存缓存的使用量超出了正常范围。对于这个问题,我们可以采取以下步骤进行排查和解决: 1. 确认问题:通过查看系统的内存使用情况,特别是slab缓存的使用量,可以使用命令“free -m”或“top”来查看。如果slab缓存占用的内存比较大,可能存在问题。 2. 检查slab缓存类型:使用命令“slabtop”可以查看slab缓存的类型及其占用的内存大小。特别是注意查看占用内存最大的缓存类型。 3. 检查进程和应用程序:有些进程或应用程序可能会频繁创建和释放内存对象,导致slab缓存过度膨胀。通过使用工具如“top”或“ps aux”命令,找出占用内存较多的进程或应用程序,并分析它们的内存使用模式。 4. 检查内核模块和驱动程序:一些内核模块或驱动程序可能会导致slab缓存异常增长。可以通过查看“lsmod”命令列出的加载的内核模块,或使用“lsof”命令查看打开的文件和网络连接来进行排查。 5. 限制slab缓存的大小:在某些情况下,可以通过修改内核参数来限制slab缓存的大小。具体的参数设置可以参考相关的文档或咨询Linux系统管理员。 6. 更新或回滚内核版本:有时,slab缓存问题可能是由于特定内核版本的bug所致。如果发现问题出现在特定的内核版本上,可以尝试升级或回滚到另一个内核版本来解决问题。 通过以上步骤,并结合具体的使用情况和系统配置,我们应该能够找出并解决“slab占用过高”的问题。如果问题仍然存在,建议进一步咨询Linux系统专家或相关技术支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值