本文介绍: 内存泄露一个很容易出现的问题,尤其是对于测试不太充分的代码。怎么判断出现内存泄露了呢?很简单,就跑一些简单测试等待足够长时间即可。内存总有耗尽的时候,这时候内核触发OOM,根据oom_score选择一个进程杀掉。这种时候,多半是有问题了。或者在某个进程运行的时候,看观察空闲内存部分,一直下降多半是有问题了。这就确定有内存泄露了,可能是用户进程泄露,也可能是内核泄露。现在确定了内核泄露方法接下来怎么找到泄漏点呢,下面会介绍几种调试方法

内存泄露是一个很容易出现的问题,尤其是对于测试不太充分的代码。怎么判断出现内存泄露了呢?很简单,就跑一些简单测试等待足够长时间即可。内存总有耗尽的时候,这时候内核触发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(&amp;list->list);
		list_add_tail(&amp;list->list, &amp;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, &amp;test_list, list)
		list_del(&amp;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禁用
# 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工作流程大概分为三部分:标记分配检测释放和报告泄露。

  1. 标记分配的内存:kmemleak 会跟踪内核中的动态内存分配,包括 kmalloc、kzalloc、vmalloc 等函数分配的内存块。标记这些分配的内存块,并记录它们的地址大小

  2. 检测释放:当内存块被释放时,kmemleak 会记录释放的内存块,并在一段时间检查是否仍然存在对这些内存块的引用

  3. 报告泄漏:扫描内存并报告这些内存泄漏,扫描方法如下

内核中本身有一些内存是不需要释放的,比如核心相关的部分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进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注