最近修复了一个 Kernel Module 内存占用高且不释放的问题,学习了一下内核态应用内存问题排查的一些工具和方法,记录一下。
我司自研了一套高性能的分布式存储系统,客户端侧使用 Kernel Module 进行挂载。使用压测程序打开并关闭 500w 文件后,内存占用上升大约 160GB,且长时间不释放。
怀疑是 Kernel Module 里内存泄漏。做了一些实验并观察现象:
sudo slaptop
观察到主要是 kmalloc-32
的上涨,随文件数量线性增长怀疑是打开文件时有内存泄漏,由于代码量巨大不好直接通过代码定位问题,Mentor 建议我用 kmemleak 找找泄漏的位置。
重新编译了一版带 kmemleak 特性的内核并安装,按如下步骤使用:
# 1. 清理当前记录的 kmemleak 信息避免干扰
echo clear | sudo tee /sys/kernel/debug/kmemleak
# 2. 使用压测工具打开大量文件。注意 kmemleak 会使用大量内存记录内存申请释放的信息,注意别 OOM
# 3. 触发 kmemleak 扫描
echo scan | sudo tee /sys/kernel/debug/kmemleak
# 4. 获取 kmemleak 记录的堆栈信息
sudo cat /sys/kernel/debug/kmemleak > result.txt
# 5. 简单统计下高频函数
cat result.txt | grep "Call Trace" -A 30 | awk '{print $6}' | awk -F '+' '{print $1}' | sort | uniq -c | sort -n -k 1
然而 kmemleak 并没有发现什么内存泄漏,也就是说这里可能并不是泄漏。山重水复疑无路,在进行一些无关的操作后,kmalloc-32
的内存占用竟然自己慢慢下降了,所以这不是泄漏!
重新执行压测程序复现问题。kmalloc-32
是使用 Slab Alloctor 分配的,但 Reclaimable 的内存占用并不多,大部分仍然是 Unreclaim 的。尝试性地执行 Drop Caches:
# 1. 释放 dentries 和 inodes
echo 2 | sudo tee /proc/sys/vm/drop_caches
# 2. 观察 Slab 内存使用情况
watch -n 1 "cat /proc/meminfo | grep Slab -A 2"
可以观察到 Reclaimable
和 kmalloc-32
的内存以几十 MB 的速度逐渐下降!虽然可以稳定触发,但清理速度过慢并不能解决业务的需求。故仍需要进一步定位是谁在申请这些小内存。
Mentor 给我指了一条明路,使用 echo 1 | sudo tee /sys/kernel/slab/kmalloc-32/trace
追踪所有的 kmalloc-32
内存申请,执行压测程序一段时间后再关掉,然后统计 dmesg
中打印的堆栈信息,终于让我找到了罪魁祸首。
在打开文件时,代码中有一处使用双向链表储存一组元信息,该元信息最终挂在 inode
上。内核会缓存大量的 inode
并且不主动释放。这些元信息使用链表存储效率非常低,有大量的内存碎片。最终将链表修改为数组后,打开并关闭 500w 文件内存上涨约 1GB,问题解决。