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