Linux内存泄露之kmemleak原理分析与使用

本文详细介绍了kmemleak的原理,包括其扫描区域、扫描过程和API接口。kmemleak通过分析内存块的引用情况来检测内核中的内存泄露,涉及bss、data、vmalloc、memory、heap和vmemmap区域。文章还提供了使用方法和限制条件。

1. kmemleak原理:

 通过分析内存块是否存在引用(指针)来判断内存泄露.

1.1  扫描区域

  首先要理解整个内核虚拟地址空间是怎么分布的,

内核地址空间分布:

Virtual kernel memory layout:
vmalloc : 0xffffff8000000000 - 0xffffffbdbfff0000   (   246 GB)
 vmemmap : 0xffffffbdc0000000 - 0xffffffbfc0000000   (     8 GB maximum)
              0xffffffbdc0000000 - 0xffffffbde4000000   (   576 MB actual)
   fixed   : 0xffffffbffa7fd000 - 0xffffffbffac00000   (  4108 KB)
   PCI I/O : 0xffffffbffae00000 - 0xffffffbffbe00000   (    16 MB)
   modules : 0xffffffbffc000000 - 0xffffffc000000000   (    64 MB)
   memory  : 0xffffffc000000000 - 0xffffffc900000000   ( 36864 MB)
   .init : 0xffffffc0010b6000 - 0xffffffc001178000   (   776 KB)

 .data

.bss

那么,我们的变量可以存在哪些区域了?

首先,bss和data存了全局变量和静态变量,栈里面存了局部变量, 堆里面也存在变量的可能.以及vmalloc区域
所以扫描区域为bss,data,vmalloc,memory,heap,vmemmap(struct page区域)

1.2 扫描过程

  kmemleak定义了两个全局链表object_list和gray_list,和一颗红黑树(用于查找).

object_list包含全部的object,而gray_list是有引用的object集合

kmemleak会启动kmemleak_scan_thread10分钟扫描一次,当然也可以手动触发扫描.

static void kmemleak_scan(void)
{
	unsigned long flags;
	struct kmemleak_object *object;
	int i;
	int new_leaks = 0;

	jiffies_last_scan = jiffies;
	/* prepare the kmemleak_object's */
	rcu_read_lock();
	list_for_each_entry_rcu(object, &object_list, object_list) {
		spin_lock_irqsave(&object->lock, flags);
		/*标记所有的object为未引用状态 */
		object->count = 0;
		/*如果设置了object为no leak标志,则直接添加到gray_list引用链表 */
		if (color_gray(object) && get_object(object))
			list_add_tail(&object->gray_list, &gray_list);

		spin_unlock_irqrestore(&object->lock, flags);
	}
	rcu_read_unlock();

	/* 扫描data和bss段 */
	scan_large_block(_sdata, _edata);
	scan_large_block(__bss_start, __bss_stop);

#ifdef CONFIG_SMP
	/* 扫描per cpu数据段 */
	for_each_possible_cpu(i)
		scan_large_block(__per_cpu_start + per_cpu_offset(i),
				 __per_cpu_end + per_cpu_offset(i));
#endif

	/*
	 这里扫描vmemmap区域,因为struct page也可能包含引用(指针)
	 */
	get_online_mems();
	for_each_online_node(i) {
		unsigned long start_pfn = node_start_pfn(i);
		unsigned long end_pfn = node_end_pfn(i);
		unsigned long pfn;
		
		for (pfn = start_pfn; pfn < end_pfn; pfn++) {
			struct page *page;

			if (!pfn_valid(pfn))
				continue;
			page = pfn_to_page(pfn);
			/* only scan if page is in use */
			if (page_count(page) == 0)
				continue;
			
			
			scan_block(page, page + 1, NULL);
		}
	}
	put_online_mems();

	/*
	 扫描每个进程的堆栈,
	 */
	if (kmemleak_stack_scan) {
		struct task_struct *p, *g;

		read_lock(&tasklist_lock);
		do_each_thread(g, p) {
			scan_block(task_stack_page(p), task_stack_page(p) +
				   THREAD_SIZE, NULL);
		} while_each_thread(g, p);
		read_unlock(&tasklist_lock);
	}

	/*
	 扫描object的内存区域,因为一个object内部,可能也会包含其他object的引用.
	 */
	scan_gray_list();

	
	rcu_read_lock();
	/*这里通过对object的内存区做crc校验,如果内存内容有修改,证明也存在引用 */
	list_for_each_entry_rcu(object, &object_list, object_list) {
		spin_lock_irqsave(&object->lock, flags);
		if (color_white(object) && (object->flags & OBJECT_ALLOCATED)
		    && update_checksum(object) && get_object(object)) {
			/* color it gray temporarily */
			object->count = object->min_count;
			list_add_tail(&object->gray_list, &gray_list);
		}
		spin_unlock_irqrestore(&object->lock, flags);
	}
	rcu_read_unlock();

	/*
	 * 再一次扫描object
	 */
	scan_gray_list();

	
	rcu_read_lock();
	list_for_each_entry_rcu(object, &object_list, object_list) {
		spin_lock_irqsave(&object->lock, flags);
		/*unreferenced object判断:1,object标记为白色(没有引用),2,有OBJECT_ALLOCATED标记,3,内存分配时间和这次扫描时间间隔5秒 */
		if (unreferenced_object(object) &&
		    !(object->flags & OBJECT_REPORTED)) {
			object->flags |= OBJECT_REPORTED;
			new_leaks++;
		}
		spin_unlock_irqrestore(&object->lock, flags);
	}
	rcu_read_unlock();

}

2. kmemleak API接口

 kmemleak_alloc //打桩函数,内存分配时调用

   kmemleak_free//内存是否时调用

kmemleak_not_leak//标记object不是内存泄露(不会report,但是会scan,因为可能包含其他object引用)

kmemleak_ignore// 标记object不是内存泄露(不会report,也不会scan)

kmemleak_no_scan//标记不要扫描object内存区(report,但会scan)

3. 使用

1.进行各种测试(monkey等待)

2.echo scan > /sys/kernel/debug/kmemleak

      cat /sys/kernel/debug/kmemleak > /sdcard/memleak_report.txt

常用指令

echo clear //标记当前object为引用状态

echo scan //启动scan线程

echo off //禁止disable memleak功能

echo scan=on //启动scan thread

echo stack=on//扫描堆栈

echo stack=off //不扫描堆栈

 

4 kmemleak使用范围

 1. 只能检查kmalloc/vmalloc/memblock方式分配的内存泄露问题,而alloc_pages/__get_free_pages/dma_alloc_coherent并不能检查

2.如果把虚拟地址转换成物理地址保存,kmemleak会报内存泄露.

 

 

 

### 启用和使用 kmemleak 检测 Linux 内核内存泄漏 kmemleakLinux 内核中用于检测内存泄漏的工具,它能够追踪 `kmalloc()`、`vmalloc()`、`kmem_cache_alloc()` 等内存分配函数所分配的内存块,并记录其地址、大小、调用栈等信息[^1]。通过周期性地扫描系统内存,kmemleak 会检测那些已分配但不再被引用的内存块,从而识别潜在的内存泄漏。 #### 内核配置 在启用 kmemleak 之前,必须确保内核配置中启用了该功能。通常需要在编译内核时设置以下配置选项: ```bash CONFIG_DEBUG_KMEMLEAK=y ``` 此外,还可以启用调试选项以获取更详细的信息: ```bash CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=n ``` 这将使 kmemleak 在系统启动时默认启用。如果希望在运行时动态控制 kmemleak 的启用状态,可以通过内核启动参数进行设置。 #### 用户空间配置 kmemleak 提供了一个用户空间接口,位于 `/sys/kernel/debug/kmemleak`。为了访问该接口,必须确保 debugfs 文件系统已经挂载: ```bash mount -t debugfs none /sys/kernel/debug ``` 一旦挂载完成,可以通过以下方式 kmemleak 交互: - **手动触发扫描**: ```bash echo scan > /sys/kernel/debug/kmemleak ``` - **查看泄漏报告**: ```bash cat /sys/kernel/debug/kmemleak ``` 该命令将输出所有疑似泄漏的内存信息,包括地址、大小、调用栈等。 - **清除泄漏列表**: ```bash echo clear > /sys/kernel/debug/kmemleak ``` 这将清除当前的泄漏记录,便于后续重新扫描。 #### 内核启动参数控制 可以通过在内核启动参数中添加 `kmemleak=on` 或 `kmemleak=off` 来控制 kmemleak 的启用状态。例如,在 GRUB 配置文件中添加: ```bash GRUB_CMDLINE_LINUX_DEFAULT="quiet splash kmemleak=on" ``` 这样可以在系统启动时启用 kmemleak,从而在系统运行早期就开始追踪内存分配。 #### 工作原理 kmemleak 的实现基于插桩机制。每当调用 `kmalloc()`、`vmalloc()` 等分配函数时,kmemleak 会调用 `kmemleak_alloc` 函数,记录分配的内存地址、大小、调用栈等信息,并将这些信息存储在一个红黑树中。当内存被释放时,kmemleak 会调用 `kmemleak_free` 函数,从红黑树中删除对应的记录[^3]。 kmemleak 会定期(默认每 10 分钟)扫描内核内存区域,包括数据段、栈和堆,以查找是否仍然存在指向红黑树中记录的内存地址。如果某个地址在内存中找不到任何引用,则认为该内存块已经泄漏[^4]。 #### 使用注意事项 - **误报可能性**:由于 kmemleak 无法区分内存中的指针和非指针数据,因此可能会产生误报。例如,如果某个内存块中的某个字段恰好另一个内存块的地址相同,那么即使该内存块已经不再被使用kmemleak 也可能不会将其标记为泄漏。 - **性能影响**:启用 kmemleak 会对系统性能产生一定影响,特别是在内存分配频繁的情况下。因此,在生产环境中不建议长期启用该功能。 - **调试辅助**:kmemleak 提供的调用栈信息可以帮助定位内存泄漏的具体位置。通过查看 `/sys/kernel/debug/kmemleak` 中的输出,可以找到分配内存的调用路径,从而进一步分析代码逻辑。 #### 示例操作 以下是一个完整的操作流程示例: 1. **挂载 debugfs**: ```bash mount -t debugfs none /sys/kernel/debug ``` 2. **触发一次内存扫描**: ```bash echo scan > /sys/kernel/debug/kmemleak ``` 3. **查看泄漏报告**: ```bash cat /sys/kernel/debug/kmemleak ``` 输出示例: ``` unreferenced object 0xffff88003c7c0000 (size 2048): comm "modprobe", pid 1234, jiffies 4294967295 (age 10 min) hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace: [<ffffffff81012345>] my_module_init+0x123/0x456 [my_module] [<ffffffff81001234>] do_one_initcall+0x56/0x78 [<ffffffff81001234>] do_init_module+0x123/0x456 ... ``` 4. **清除泄漏记录**: ```bash echo clear > /sys/kernel/debug/kmemleak ``` 通过上述步骤,可以有效地使用 kmemleak 工具来检测 Linux 内核中的内存泄漏问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值