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

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也不会报内存泄露.

猜你喜欢

转载自blog.csdn.net/bin_linux96/article/details/84969544