Kernel Module 内存问题排查

2022.01.11
SF-Zhou

最近修复了一个 Kernel Module 内存占用高且不释放的问题,学习了一下内核态应用内存问题排查的一些工具和方法,记录一下。

1. 问题简述

我司自研了一套高性能的分布式存储系统,客户端侧使用 Kernel Module 进行挂载。使用压测程序打开并关闭 500w 文件后,内存占用上升大约 160GB,且长时间不释放。

2. 初步分析

怀疑是 Kernel Module 里内存泄漏。做了一些实验并观察现象:

  1. 打开并关闭 100w 文件,内存增加 32GB 左右;重新打开并关闭这 100w 文件,内存无上涨;打开另一批 100w 文件,内存增加到 64GB 水平
  2. 使用 sudo slaptop 观察到主要是 kmalloc-32 的上涨,随文件数量线性增长

怀疑是打开文件时有内存泄漏,由于代码量巨大不好直接通过代码定位问题,Mentor 建议我用 kmemleak 找找泄漏的位置。

3. Kernel Memory Leak Detector

重新编译了一版带 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 的内存占用竟然自己慢慢下降了,所以这不是泄漏!

4. Slab Alloctor

重新执行压测程序复现问题。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"

可以观察到 Reclaimablekmalloc-32 的内存以几十 MB 的速度逐渐下降!虽然可以稳定触发,但清理速度过慢并不能解决业务的需求。故仍需要进一步定位是谁在申请这些小内存。

5. Slab Trace

Mentor 给我指了一条明路,使用 echo 1 | sudo tee /sys/kernel/slab/kmalloc-32/trace 追踪所有的 kmalloc-32 内存申请,执行压测程序一段时间后再关掉,然后统计 dmesg 中打印的堆栈信息,终于让我找到了罪魁祸首。

在打开文件时,代码中有一处使用双向链表储存一组元信息,该元信息最终挂在 inode 上。内核会缓存大量的 inode 并且不主动释放。这些元信息使用链表存储效率非常低,有大量的内存碎片。最终将链表修改为数组后,打开并关闭 500w 文件内存上涨约 1GB,问题解决。

References

  1. "Kernel Memory Leak Detector", The kernel development community
  2. "proc(5) — Linux manual page", Linux Programmer's Manual