记录一次kernel内存泄漏的查找定位过程

本文记录了一个在压力测试过程中遇到的内存泄漏问题的定位过程。通过对工程模块的逐步分解和内存状态的深入分析,最终定位到内核空间的一个函数,发现由于函数调用顺序问题导致的内存泄漏。

Bug描述:压力测试一个小工程时发现内存逐渐减少,10个小时后出现OOM

Bug定位过程:

  • 对整个工程模块进行分解,逐步缩小范围,由于整个工程包括几个相对独立的小模块,而整个工程采用单进程多线程的模型,导致进行分解时,要特别注意相互之间的耦合,只能逐步分离各个模块,运行测试(这里如果采用多进程模型,定位会更快一些,一个完整的功能,放在一个进程和多进程中,多进程天然的将功能细化了,定位问题,范围更小)
  • 在经过一段折磨人的拆分过程后,最后把问题定位到整个工程中一个小模块功能内。在对该模块进行了反复的代码review后,没有发现什么异常,甚至没有内存申请的操作。
  • 代码层面没有找到突破的情况下,重新通过各种命令查看了内存状态,由于在此之前一直通过free命令查看内存,发现长时间后free命令输出的可用内存在逐渐减少,但忽略了一点:通过top命令单独查看模块进程占用的内存时,该进程的rss段一直保持稳定,没有大幅度增长。
  • 基于前一步的发现,怀疑是kernel的内存有泄漏,查看/proc/meminfo发现一个疑点:slab内存占用很高,且SUnreclaim的slab一直在增加,此时基本确定kernel内存泄漏。
  • 通过kmemleak对内核内存进行了分析,定位在到一个函数接口中:
char *wr_pr_debug_begin(u8 const *data, u32 len, char *string)
{
    int ii;
    string = kmalloc(len * 2 + 1, GFP_KERNEL);
    for (ii = 0; ii < len; ii++)
        sprintf(&string[ii * 2], "%02X", data[ii]);
    string[len * 2] = 0;
    return string;
}
char *wr_pr_debug_end(char *string)
{
    kfree(string);
    return "";
}
void test()
{
    char *read = 0;
    pr_debug("%s RD%02X%02X%02X -> %s%s\n", st->hw->name,
         i2c_addr, reg, length,
         wr_pr_debug_begin(data, length, read),
         wr_pr_debug_end(read));
}

一眼可能不容易看出上面的有什么问题,有kmalloc,有kfree啊,好像成对出现的。
考验基本功的时候到了,熟悉函数调用传参的人应该会知道编译器一般对参数的处理采用堆栈的方式,是一个先进后出的过程,这样参数的执行一般是逆序的(由于编译器实现的不同,这个过程不是确定的),这样kfree会在kmalloc之前运行,导致每次运行都会泄漏一点内存。上面是一个debug输出,暂时注释掉后压测,问题解决,内存保持稳定。

总结:整个定位过程其实比较简单,如果第一步看下/proc/meminfo可能会更快的定位问题(由于这个kernel driver是“大厂”提供,以为不会出问题,一直从上层的角度去找问题,所以没有太关注kernel相关内存的使用),导致内存泄漏的原因也很简单,出现这种问题的原因,首先编写者的基本功一般,更主要的原因是编写者出于“炫技”的方式去写了这段代码,如果老老实实封装一个debug函数,按照正常顺序调用也就没有问题了,而且这种每次打印进行kmalloc的方式,对性能也是有些影响的。总之基本功还是很重要,而且不要驾驭自己驾驭不了的编码方式。

### 3.1 内存泄漏在应用层的表现 在Linux系统中,驱动程序运行于内核空间[^2],其内存管理与用户空间的应用程序是隔离的。因此,驱动程序的内存泄漏不会直接表现为某个具体进程的内存占用增长。然而,系统整体的可用内存会随着时间推移而减少,这可以通过监控系统级内存使用情况来间接判断。 例如,可以使用如下命令持续观察系统内存消耗排名靠前的进程: ```bash while true; do ps -e -o pid,comm,%mem --sort -%mem | head -n 10; echo "----------"; sleep 1; done ``` 该命令可以帮助识别是否有系统组件(如驱动模块)导致了全局性的内存下降[^3]。 ### 3.2 使用系统监控工具检测内存趋势 除了上述基于 `ps` 的方法外,还可以使用 `top`、`htop` 或 `free` 等工具查看系统的整体内存使用趋势。如果发现系统空闲内存持续下降且无法回收,则可能是由于内核空间的内存泄漏所引起。 此外,`slabtop` 命令可以用于查看内核中各种缓存对象的分配情况,包括由驱动程序申请的内存块。若某些内核对象的数量异常增长,可能暗示存在内存泄漏问题。 ### 3.3 利用 kmemleak 检测内核内存泄漏 对于内核模块或驱动程序的内存泄漏,可以启用 `kmemleak` 功能进行检测。它通过扫描内存分配和释放记录来识别未被释放的内存块,并输出疑似泄漏的地址信息。启用方式为: ```bash echo 1 > /sys/kernel/debug/kmemleak ``` 之后,查看 `/sys/kernel/debug/kmemleak` 文件即可获取潜在的内存泄漏报告: ```bash cat /sys/kernel/debug/kmemleak ``` 需要注意的是,`kmemleak` 只能提供静态分析结果,不能实时跟踪动态分配行为。因此,在实际使用中应结合日志和调试信息进一步定位问题源[^1]。 ### 3.4 配合 dmesg 查看内核日志 在驱动开发过程中,应确保内存申请函数与释放函数成对出现,并合理处理所有错误路径。若发生内存泄漏,内核可能会在日志中记录相关警告信息。使用 `dmesg` 命令可查看这些日志: ```bash dmesg | grep -i memory ``` ### 3.5 用户空间辅助调试机制 虽然用户空间无法直接访问内核内存,但可以通过 `ioctl` 或设备文件等接口与驱动交互,并在驱动中加入调试输出机制。例如,在调用 `kmalloc` 和 `kfree` 时打印日志,记录每次内存分配与释放的信息,从而帮助分析是否出现了未释放的内存操作。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值