0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内存泄漏检测实现原理与实现

Linux阅码场 来源:Linux内核那些事 2023-07-03 09:21 次阅读

在使用没有垃圾回收的语言时(如 C/C++),可能由于忘记释放内存而导致内存被耗尽,这叫内存泄漏。由于内核也需要自己管理内存,所以也可能出现内存泄漏的情况。为了能够找出导致内存泄漏的地方,Linux 内核开发者开发出 kmemleak 功能。

下面我们来详细介绍一下 kmemleak 这个功能的原理与实现。

kmemleak 原理

首先来分析一下,什么情况会导致内存泄漏。

1. 造成内存泄漏的原因

内存泄漏的根本原因是由于用户没有释放不再使用的动态申请的内存(在内核中由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数申请的内存),那么哪些内存是不再使用的呢?一般来说,没有被指针引用(指向)的内存都是不再使用的内存。因为这些内存已经丢失了其地址信息,从而导致内核不能再使用这些内存。

我们来看看下图的事例:

00123232-1935-11ee-962d-dac502259ad0.png

如上图所示,指针A原来指向内存块A,但后来指向新申请的内存块B,从而导致内存块A的内存地址信息丢失。如果此时用户没有及时释放掉内存块A,就会导致内存泄漏。

当然少量的内存泄漏并不会造成很严重的效果,但如果是频发性的内存泄漏,将会造成系统内存资源耗尽,从而导致系统崩溃。

2. 内核中的指针

既然没有指针引用的内存属于泄漏的内存,那么只需要找出系统是否存在没有指针引用的内存,就可以判断系统是否存在内存泄漏。

那么,怎么找到内核中的所有指针呢?我们知道,指针一般存放在内核数据段、内核栈和动态申请的内存块中。如下图所示:

003effce-1935-11ee-962d-dac502259ad0.png

但内核并没有对指针进行记录,也就是说内核并不知道这些区域是否存在指针。那么内核只能够把这些区域当成是由指针组成的,也就是说把这些区域中的每个元素都当成是一个指针。如下图所示:

0080d91c-1935-11ee-962d-dac502259ad0.png

当然,把所有元素都当成是指针是一个假设,所以会存在误判的情况。不过这也没关系,因为kmemleak这个功能只是为了找到内核中疑似内存泄漏的地方。

3. 记录动态内存块

前面说过,kmemleak 机制用于分析由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数申请的内存是否存在泄漏。

分析的依据是:扫描内核中所有的指针,然后判断这些指针是否指向了由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数申请的内存块。如果存在没有指针引用的内存块,那么就表示可能存在内存泄漏。

所以,当使用memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数申请内存时,内核会把申请到的内存块信息记录下来,用于后续扫描时使用。内核使用kmemleak_object对象来记录这些内存块的信息,然后通过一棵红黑树把这些kmemleak_object对象组织起来(使用内存块的地址作为键),如下图所示:

009bd0b4-1935-11ee-962d-dac502259ad0.png

所以内存泄漏检测的原理是:

遍历内核中所有的指针,然后从红黑树中查找是否存在对应的内存块,如果存在就把内存块打上标记。

所有指针扫描完毕后,再遍历红黑树中所有kmemleak_object对象。如果发现没有打上标记的内存块,说明存在内存泄漏(也就是说,存在没有被指针引用的内存块),并且将对应的内存块信息记录下来。

kmemleak 实现

了解了 kmemleak 机制的原理后,现在我们来分析其代码实现。

1. kmemleak_object 对象

上面介绍过,内核通过kmemleak_object对象来记录动态内存块的信息,其定义如下:

structkmemleak_object{
spinlock_tlock;
unsignedlongflags;/*objectstatusflags*/
structlist_headobject_list;
structlist_headgray_list;
structrb_noderb_node;
...
atomic_tuse_count;
unsignedlongpointer;
size_tsize;
intmin_count;
intcount;
...
pid_tpid;/*pidofthecurrenttask*/
charcomm[TASK_COMM_LEN];/*executablename*/
};

kmemleak_object对象的成员字段比较多,现在我们重点关注rb_node、pointer和size这 3 个字段:

rb_node:此字段用于将kmemleak_object对象连接到红黑树中。

pointer:用于记录内存块的起始地址。

size:用于记录内存块的大小。

内核就是通过这 3 个字段,把kmemleak_object对象连接到全局红黑树中。

例如利用kmalloc函数申请内存时,最终会调用create_object来创建kmemleak_object对象,并且将其添加到全局红黑树中。我们来看看create_obiect函数的实现,如下:

...
//红黑树的根节点
staticstructrb_rootobject_tree_root=RB_ROOT;
...

staticstructkmemleak_object*
create_object(unsignedlongptr,size_tsize,intmin_count,gfp_tgfp)
{
unsignedlongflags;
structkmemleak_object*object,*parent;
structrb_node**link,*rb_parent;

//申请一个新的kmemleak_object对象
object=kmem_cache_alloc(object_cache,gfp_kmemleak_mask(gfp));
...
object->pointer=ptr;
object->size=size;

//将新申请的kmemleak_object对象添加到全局红黑树中
...
link=&object_tree_root.rb_node;//红黑树根节点
rb_parent=NULL;

//找到kmemleak_object对象插入的位置(参考平衡二叉树的算法)
while(*link){
rb_parent=*link;
parent=rb_entry(rb_parent,structkmemleak_object,rb_node);
if(ptr+size<= parent->pointer)
link=&parent->rb_node.rb_left;
elseif(parent->pointer+parent->size<= ptr)
            link = &parent->rb_node.rb_right;
else{
...
gotoout;
}
}

//将kmemleak_object对象插入到红黑树中
rb_link_node(&object->rb_node,rb_parent,link);
rb_insert_color(&object->rb_node,&object_tree_root);

out:
...
returnobject;
}

虽然create_obiect函数的代码比较长,但是逻辑却很简单,主要完成 2 件事情:

申请一个新的kmemleak_object对象,并且初始化其各个字段。

将新申请的kmemleak_object对象添加到全局红黑树中。

将kmemleak_object对象插入到全局红黑树的算法与数据结构中的平衡二叉树算法是一致的,所以不了解的同学可以查阅相关的资料

2. 内存泄漏检测

当开启内存泄漏检测时,内核将会创建一个名为kmemleak的内核线程来进行检测。

在分析内存检测的实现之前,我们先来了解一下关于kmemleak_object对象的三个概念:

白色节点:表示此对象没有被指针引用(count字段少于min_count字段)。

灰色节点:表示此对象被一个或多个指针引用(count字段大于或等于min_count字段)。

黑色节点:表示此对象不需要被扫描(min_count字段等于 -1)。

接着我们来看看kmemleak内核线程的实现:

staticintkmemleak_scan_thread(void*arg)
{
...
while(!kthread_should_stop()){
...
kmemleak_scan();//进行内存泄漏扫描
...
}
return0;
}

可以看出kmemleak内核线程主要通过调用kmemleak_scan函数来进行内存泄漏扫描。我们继续来看看kmemleak_scan函数的实现:

staticvoidkmemleak_scan(void)
{
...
//1)将所有kmemleak_object对象的count字段置0,表示开始时全部是白色节点
list_for_each_entry_rcu(object,&object_list,object_list){
...
object->count=0;
...
}
...

//2)扫描数据段与未初始化数据段
scan_block(_sdata,_edata,NULL,1);
scan_block(__bss_start,__bss_stop,NULL,1);
...

//3)扫描所有内存页结构,这是由于内存页结构也可能引用其他内存块
for_each_online_node(i){
...
for(pfn=start_pfn;pfn< end_pfn; pfn++) {
            ...
            page = pfn_to_page(pfn);
            ...
            scan_block(page, page + 1, NULL, 1);
        }
    }
    ...

    // 4) 扫描所有进程的内核栈
    if (kmemleak_stack_scan) {
        ...
        do_each_thread(g, p) {
            scan_block(task_stack_page(p), task_stack_page(p) + THREAD_SIZE, NULL, 0);
        } while_each_thread(g, p);
        ...
    }

    // 5) 扫描所有灰色节点
    scan_gray_list();
    ...
}

由于kmemleak_scan函数的代码比较长,所以我们对其进行精简。精简后可以看出,kmemleak_scan函数主要完成 5 件事情:

将系统中所有kmemleak_object对象的count字段置 0,表示扫描开始时,所有节点都是白色节点。

调用scan_block函数扫描数据段与未初始化数据段,因为这两个区域可能存在指针。

扫描所有内存页结构,这是因为内存页结构可能会引用其他内存块,所以也要对其进行扫描。

扫描所有进程内核栈,由于进程内核栈可能存在指针,所以要对其进行扫描。

扫描所有灰色节点,由于灰色节点也可能存在指针,所以要对其进行扫描。

扫描主要通过scan_block函数进行,我们来看看scan_block函数的实现:

staticvoid
scan_block(void*_start,void*_end,structkmemleak_object*scanned,
intallow_resched)
{
unsignedlong*ptr;
unsignedlong*start=PTR_ALIGN(_start,BYTES_PER_POINTER);
unsignedlong*end=_end-(BYTES_PER_POINTER-1);

//对内存区进行扫描
for(ptr=start;ptr< end; ptr++) {
        struct kmemleak_object *object;
        unsigned long flags;
        unsigned long pointer;
        ...

        pointer = *ptr;

        // 查找指针所引用的内存块是否存在于红黑树中,如果不存在就跳过此指针
        object = find_and_get_object(pointer, 1);
        if (!object)
            continue;
        ...
        // 如果对象不是白色,说明此内存块已经被指针引用
        if (!color_white(object)) {
            ...
            continue;
        }

        // 对 kmemleak_object 对象的count字段进行加一操作
        object->count++;

//判断当前对象是否灰色节点,如果是将其添加到灰色节点链表中
if(color_gray(object)){
list_add_tail(&object->gray_list,&gray_list);
...
continue;
}
...
}
}

scan_block函数主要完成以下几个步骤:

遍历内存区所有指针。

查找指针所引用的内存块是否存在于红黑树中,如果不存在就跳过处理此对象。

如果kmemleak_object对象不是白色,说明已经有指针引用此内存块,跳过处理此对象。

对kmemleak_object对象的count字段进行加一操作,表示有指针引用此内存块。

判断当前kmemleak_object对象是否是灰色节点(count字段大于或等于min_count字段),如果是将其添加到灰色节点链表中。

扫描完毕后,所有白色的节点就是可能存在内存泄漏的内存块。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux系统
    +关注

    关注

    4

    文章

    593

    浏览量

    27392
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6989
收藏 人收藏

    评论

    相关推荐

    Linux内存泄漏检测实现原理与实现

    在使用没有垃圾回收的语言时(如 C/C++),可能由于忘记释放内存而导致内存被耗尽,这叫 内存泄漏。由于内核也需要自己管理内存,所以也可能出
    发表于 12-09 11:11 969次阅读

    细说Linux内存泄漏检测实现原理与实现

    在使用没有垃圾回收的语言时(如 C/C++),可能由于忘记释放内存而导致内存被耗尽,这叫 内存泄漏。由于内核也需要自己管理内存,所以也可能出
    发表于 07-03 09:22 476次阅读
    细说<b class='flag-5'>Linux</b><b class='flag-5'>内存</b><b class='flag-5'>泄漏检测</b><b class='flag-5'>实现</b>原理与<b class='flag-5'>实现</b>

    煤气泄漏检测系统!毕业设计

    煤气泄漏检测系统!毕业设计,高手请帮忙!
    发表于 03-24 01:48

    如何去解决电信设备内的泄漏检测

    基于电信设备内液体泄漏检测的光电液位传感器用于昂贵和关键系统的低成本泄漏检测解决方案
    发表于 02-23 06:34

    写了一个内存泄漏检查工具

    嵌入式环境内存泄漏检查比较麻烦,valgrind比较适合于在pc上跑,嵌入式上首先移植就很麻烦,移植完了内存比较小,跑起来也比较费劲。所以手动写了一个内存
    发表于 12-17 08:25

    泄漏检测仪校正与调整

    本文概述了泄漏检测仪的基本结构,针对泄漏检测仪出现“误判”故障,在校正及修理调试时采取了相应措施,恢复了泄漏检测仪正常使用功能。
    发表于 01-14 15:29 13次下载

    泄漏检测技术

    从割草机到咖啡机,任何的流体处理设备都需要进行泄漏检测,从而为其投入市场做论证准备。通常,应用在样机设计阶段的泄漏检测方法也是在大批量生产中用于检测的最好方法
    发表于 01-23 12:04 13次下载

    泄漏检测及定位原理

    泄漏检测及定位原理 当管 道 发 生泄漏时,泄漏点处由于管道内外的压差,流体迅速消失,压力下降。泄漏点两边的流体由于存在压差而
    发表于 01-08 11:48 1842次阅读
    <b class='flag-5'>泄漏检测</b>及定位原理

    沼气泄漏检测电路

    沼气泄漏检测电路
    发表于 02-15 13:35 529次阅读
    沼气<b class='flag-5'>泄漏检测</b>电路

    氨气泄漏的危害_氨气泄漏检测仪怎么使用_氨气泄漏检测仪的使用方法

    氨气泄漏检测仪 氨气泄漏检测仪测量范围:0-100ppm、0-400ppm,声光报警,高防水防尘设计,具有数据存储功能,声光报警。
    发表于 01-03 09:57 2733次阅读

    嵌入式装置内存泄漏检测系统设计

    ,极易出现应用程序内存泄漏内存泄漏按照发生的频率可分为常发性、偶发性、一次性以及隐式内存泄漏4
    发表于 04-26 14:35 3次下载
    嵌入式装置<b class='flag-5'>内存</b><b class='flag-5'>泄漏检测</b>系统设计

    真空泄漏检测仪的重要性和应用

    真空泄漏检测仪是一种强大的设备,它能够检测和定位系统或部件的微小泄漏。在许多行业中,包括汽车、航空航天、医疗设备和半导体等,这种设备都是必不可少的。下面我们将详细讨论真空泄漏检测仪的重
    的头像 发表于 08-15 09:52 956次阅读

    基于C++代码实现内存泄漏检测工具

    看到的一个文章,有人用一个很简短的代码实现内存检测工具,大家看看实用性如何?
    发表于 08-21 10:11 727次阅读
    基于C++代码<b class='flag-5'>实现</b><b class='flag-5'>内存</b><b class='flag-5'>泄漏检测</b>工具

    如何写一个内存泄漏检测工具

    如何确定有内存泄露问题,如何定位到内存泄露位置,如何写一个内存泄漏检测工具? 1:概述 内存泄露本质:其实就是申请调用malloc/new,
    的头像 发表于 11-11 16:19 817次阅读

    如何检测内存泄漏

    检测内存泄漏是软件开发过程中一项至关重要的任务,它有助于识别和解决那些导致程序占用过多内存资源,从而影响程序性能甚至导致程序崩溃的问题。以下将详细阐述几种常见的
    的头像 发表于 07-30 11:50 1796次阅读