内存泄露是一个很容易出现的问题,尤其是对于测试不太充分的代码。怎么判断出现内存泄露了呢?很简单,就跑一些简单的测试,等待足够长时间即可。内存总有耗尽的时候,这时候内核会触发OOM,根据oom_score选择一个进程杀掉。这种时候,多半是有问题了。
或者在某个进程运行的时候,看/proc/meminfo
观察空闲内存部分,一直下降多半是有问题了。这就确定有内存泄露了,可能是用户态进程泄露,也可能是内核泄露。可以有多种方法来分辨:
- 观察meminfo中的slab项,如果这一项有异常的增长或者只增不减一路狂飙,那八成是内核漏出去了
- OOM killer会根据oom score选择一个进程杀掉,假如选择了desktop Xserver等等,就说明找不到分更高的进程了,这么front且和用户体验相关的进程nice值是很低的。这都能选上,那就是用户态进程没啥占内存特别高的进程了,差不多也能确定是内核泄露了。
- OOM killer杀了进程之后,观察OOM打出来的内存信息,并没有收回来多少。内核的内存泄露,不管OOM选择的是哪个进程来杀,内存都收不回来的,oom完事之后系统使用起来依然觉得卡卡的,那就是内核泄露了。
现在确定了内核泄露的方法,接下来怎么找到泄漏点呢,下面会介绍几种调试的方法。在此之前,先简单实现一个有内存泄漏的内核模块。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/list.h>
#include <linux/percpu.h>
#include <linux/fdtable.h>
struct list_node {
long header[25];
struct list_head list;
char name[25];
};
static LIST_HEAD(test_list);
/*
* Some very simple testing. This function needs to be extended for
* proper testing.
*/
static int __init module_init(void)
{
struct list_node *list;
int i;
pr_info("This is test mode for memleak debug tools!n");
/*alloc */
pr_info("kmalloc(32) = %pn", kmalloc(32, GFP_KERNEL));
pr_info("kmalloc(32) = %pn", kmalloc(32, GFP_KERNEL));
pr_info("kmalloc(1024) = %pn", kmalloc(1024, GFP_KERNEL));
pr_info("kmalloc(1024) = %pn", kmalloc(1024, GFP_KERNEL));
pr_info("kmalloc(2048) = %pn", kmalloc(2048, GFP_KERNEL));
pr_info("kmalloc(2048) = %pn", kmalloc(2048, GFP_KERNEL));
pr_info("kmalloc(4096) = %pn", kmalloc(4096, GFP_KERNEL));
pr_info("kmalloc(4096) = %pn", kmalloc(4096, GFP_KERNEL));
pr_info("vmalloc(64) = %pn", vmalloc(64));
pr_info("vmalloc(64) = %pn", vmalloc(64));
pr_info("vmalloc(64) = %pn", vmalloc(64));
pr_info("vmalloc(64) = %pn", vmalloc(64));
pr_info("vmalloc(64) = %pn", vmalloc(64));
/*
* create a list have 10 nodes
*/
for (i = 0; i < 10; i++) {
list = kzalloc(sizeof(*list), GFP_KERNEL);
pr_info("kzalloc(sizeof(*list)) = %pn", list);
if (!list)
return -ENOMEM;
INIT_LIST_HEAD(&list->list);
list_add_tail(&list->list, &test_list);
}
return 0;
}
module_init(module_init);
static void __exit module_exit(void)
{
struct test_node *list, *tmp;
//delete all node in test
list_for_each_entry_safe(list, tmp, &test_list, list)
list_del(&list->list);
}
module_exit(module_exit);
MODULE_LICENSE("GPL");
kmemleak
第一个debug的方法也是最简单的——使用Kmemleak,这是一种用于检测内核内存泄漏的工具,可以用来检测目前内核中可能存在的泄露。kmemleak 是一种运行时的工具,它可以在内核运行时进行内存泄漏检测,但也会带来一定的性能开销。因此,通常情况下发行版并不会打开此工具。
Kmemleak使用
CONFIG_DEBUG_KMEMLEAK //kmemleak总开关
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF //是不是默认禁用kmemleak,非必须;
// 打开选项之后,可以通过cmdline kmemleak=on 打开kmemleak;关闭这个选项,也可以通过cmdline kmemleak=off禁用
-
触发内存scan
模块插入后等几分钟执行echo scan > /sys/kernel/debug/kmemleak
触发一次内存scan去找可能存在的内存泄露,这是整个内存的scan,可能需要点时间。
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff89862ca702e8 (size 32):
comm "modprobe", pid 2088, jiffies 4294680594 (age 375.486s)
hex dump (first 32 bytes):
6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b kkkkkkkkkkkkkkkk
6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 kkkkkkkkkkkkkkk.
backtrace:
[<00000000e0a73ec7>] 0xffffffffc01d3446
[<000000000c5d2a46>] do_one_initcall+0x41/0x1df
[<0000000046db7e0a>] do_init_module+0x55/0x200
[<00000000542b9814>] load_module+0x203c/0x2480
[<00000000c2850256>] __do_sys_finit_module+0xba/0xe0
[<000000006564e7ef>] do_syscall_64+0x43/0x110
[<000000007c873fa6>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
calltrace最上面一行就是执行malloc的位置,可以根据addr2line或者gdb把地址和代码对应起来。是不是超级简单,超级容易。打开选项重编一个内核,就知道所有可能泄漏的位置。
为什么说可能存在的泄露位置呢,因为Kmemleak存在误报的情况。
kmemleak误报
kmemleak工作流程大概分为三部分:标记分配、检测释放和报告泄露。
-
标记分配的内存:kmemleak 会跟踪内核中的动态内存分配,包括 kmalloc、kzalloc、vmalloc 等函数分配的内存块。标记这些分配的内存块,并记录它们的地址和大小。
-
-
标记所有对象为白色:初始时,kmemleak 会将所有的内存对象标记为白色。在后续的扫描中,仍然被标记为白色的对象将被视为孤立对象(orphan)。
-
扫描内存:从数据段和栈开始扫描内存,检查其中的数值是否与存储在红黑树(rbtree)中的地址相匹配。如果发现指向白色对象的指针,则将该对象添加到灰色列表中。
-
扫描灰色对象:对灰色对象进行扫描,查找匹配的地址。一些白色对象可能会变为灰色,并被添加到灰色列表的末尾,直到灰色集合完成扫描。
-
报告孤立对象:剩余的白色对象被视为孤立对象,因为数据段和堆栈里没有指向这块空间的变量/常量,内核认为这块空间未来不会被使用,而且这时候似乎free并不能释放这块空间,白色对象通过 /sys/kernel/debug/kmemleak 报告。
-
内核中本身有一些内存是不需要释放的,比如说核心相关的部分do_init_call
函数,或者此块内存地址可以通过其他的什么方法计算得到(除了container_of,scan的流程是:假如现在扫到栈里面有一个指针指向结构体的某一个成员,整个结构体占的这块空间也会被标记为灰色,这个操作就是兼容container_of的意思),kmemleak仍然会把这块内存当泄露处理。
误报概率非常非常低,绝大部分情况是非常非常好用的!几乎一切内存泄露都逃不过kmemleak的检查,慧眼如炬明察秋毫!
然而接下来来了一个这样的场景:假如我现在的系统不方便换内核,比如说生产服务器或者拿不到内核源码,这下怎么办呢,因为性能问题Kmemleak是绝对不会在任何一个非debug发行版上开启的,这就引入了第二个方式——如何在不修改内核代码的情况下拿到内核运行的相关内容呢。
原文地址:https://blog.csdn.net/sium__/article/details/134721400
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_18511.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!