0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)存泄漏檢測實(shí)現(xiàn)原理與實(shí)現(xiàn)

Linux閱碼場 ? 來源:Linux內(nèi)核那些事 ? 2023-07-03 09:21 ? 次閱讀

在使用沒有垃圾回收的語言時(shí)(如 C/C++),可能由于忘記釋放內(nèi)存而導(dǎo)致內(nèi)存被耗盡,這叫內(nèi)存泄漏。由于內(nèi)核也需要自己管理內(nèi)存,所以也可能出現(xiàn)內(nèi)存泄漏的情況。為了能夠找出導(dǎo)致內(nèi)存泄漏的地方,Linux 內(nèi)核開發(fā)者開發(fā)出 kmemleak 功能。

下面我們來詳細(xì)介紹一下 kmemleak 這個(gè)功能的原理與實(shí)現(xiàn)。

kmemleak 原理

首先來分析一下,什么情況會導(dǎo)致內(nèi)存泄漏。

1. 造成內(nèi)存泄漏的原因

內(nèi)存泄漏的根本原因是由于用戶沒有釋放不再使用的動態(tài)申請的內(nèi)存(在內(nèi)核中由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函數(shù)申請的內(nèi)存),那么哪些內(nèi)存是不再使用的呢?一般來說,沒有被指針引用(指向)的內(nèi)存都是不再使用的內(nèi)存。因?yàn)檫@些內(nèi)存已經(jīng)丟失了其地址信息,從而導(dǎo)致內(nèi)核不能再使用這些內(nèi)存。

我們來看看下圖的事例:

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

如上圖所示,指針A原來指向內(nèi)存塊A,但后來指向新申請的內(nèi)存塊B,從而導(dǎo)致內(nèi)存塊A的內(nèi)存地址信息丟失。如果此時(shí)用戶沒有及時(shí)釋放掉內(nèi)存塊A,就會導(dǎo)致內(nèi)存泄漏。

當(dāng)然少量的內(nèi)存泄漏并不會造成很嚴(yán)重的效果,但如果是頻發(fā)性的內(nèi)存泄漏,將會造成系統(tǒng)內(nèi)存資源耗盡,從而導(dǎo)致系統(tǒng)崩潰。

2. 內(nèi)核中的指針

既然沒有指針引用的內(nèi)存屬于泄漏的內(nèi)存,那么只需要找出系統(tǒng)是否存在沒有指針引用的內(nèi)存,就可以判斷系統(tǒng)是否存在內(nèi)存泄漏。

那么,怎么找到內(nèi)核中的所有指針呢?我們知道,指針一般存放在內(nèi)核數(shù)據(jù)段、內(nèi)核棧和動態(tài)申請的內(nèi)存塊中。如下圖所示:

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

但內(nèi)核并沒有對指針進(jìn)行記錄,也就是說內(nèi)核并不知道這些區(qū)域是否存在指針。那么內(nèi)核只能夠把這些區(qū)域當(dāng)成是由指針組成的,也就是說把這些區(qū)域中的每個(gè)元素都當(dāng)成是一個(gè)指針。如下圖所示:

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

當(dāng)然,把所有元素都當(dāng)成是指針是一個(gè)假設(shè),所以會存在誤判的情況。不過這也沒關(guān)系,因?yàn)閗memleak這個(gè)功能只是為了找到內(nèi)核中疑似內(nèi)存泄漏的地方。

3. 記錄動態(tài)內(nèi)存塊

前面說過,kmemleak 機(jī)制用于分析由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函數(shù)申請的內(nèi)存是否存在泄漏。

分析的依據(jù)是:掃描內(nèi)核中所有的指針,然后判斷這些指針是否指向了由memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函數(shù)申請的內(nèi)存塊。如果存在沒有指針引用的內(nèi)存塊,那么就表示可能存在內(nèi)存泄漏。

所以,當(dāng)使用memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函數(shù)申請內(nèi)存時(shí),內(nèi)核會把申請到的內(nèi)存塊信息記錄下來,用于后續(xù)掃描時(shí)使用。內(nèi)核使用kmemleak_object對象來記錄這些內(nèi)存塊的信息,然后通過一棵紅黑樹把這些kmemleak_object對象組織起來(使用內(nèi)存塊的地址作為鍵),如下圖所示:

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

所以內(nèi)存泄漏檢測的原理是:

遍歷內(nèi)核中所有的指針,然后從紅黑樹中查找是否存在對應(yīng)的內(nèi)存塊,如果存在就把內(nèi)存塊打上標(biāo)記。

所有指針掃描完畢后,再遍歷紅黑樹中所有kmemleak_object對象。如果發(fā)現(xiàn)沒有打上標(biāo)記的內(nèi)存塊,說明存在內(nèi)存泄漏(也就是說,存在沒有被指針引用的內(nèi)存塊),并且將對應(yīng)的內(nèi)存塊信息記錄下來。

kmemleak 實(shí)現(xiàn)

了解了 kmemleak 機(jī)制的原理后,現(xiàn)在我們來分析其代碼實(shí)現(xiàn)。

1. kmemleak_object 對象

上面介紹過,內(nèi)核通過kmemleak_object對象來記錄動態(tài)內(nèi)存塊的信息,其定義如下:

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對象的成員字段比較多,現(xiàn)在我們重點(diǎn)關(guān)注rb_node、pointer和size這 3 個(gè)字段:

rb_node:此字段用于將kmemleak_object對象連接到紅黑樹中。

pointer:用于記錄內(nèi)存塊的起始地址。

size:用于記錄內(nèi)存塊的大小。

內(nèi)核就是通過這 3 個(gè)字段,把kmemleak_object對象連接到全局紅黑樹中。

例如利用kmalloc函數(shù)申請內(nèi)存時(shí),最終會調(diào)用create_object來創(chuàng)建kmemleak_object對象,并且將其添加到全局紅黑樹中。我們來看看create_obiect函數(shù)的實(shí)現(xiàn),如下:

...
//紅黑樹的根節(jié)點(diǎn)
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;

//申請一個(gè)新的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;//紅黑樹根節(jié)點(diǎn)
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函數(shù)的代碼比較長,但是邏輯卻很簡單,主要完成 2 件事情:

申請一個(gè)新的kmemleak_object對象,并且初始化其各個(gè)字段。

將新申請的kmemleak_object對象添加到全局紅黑樹中。

將kmemleak_object對象插入到全局紅黑樹的算法與數(shù)據(jù)結(jié)構(gòu)中的平衡二叉樹算法是一致的,所以不了解的同學(xué)可以查閱相關(guān)的資料。

2. 內(nèi)存泄漏檢測

當(dāng)開啟內(nèi)存泄漏檢測時(shí),內(nèi)核將會創(chuàng)建一個(gè)名為kmemleak的內(nèi)核線程來進(jìn)行檢測。

在分析內(nèi)存檢測的實(shí)現(xiàn)之前,我們先來了解一下關(guān)于kmemleak_object對象的三個(gè)概念:

白色節(jié)點(diǎn):表示此對象沒有被指針引用(count字段少于min_count字段)。

灰色節(jié)點(diǎn):表示此對象被一個(gè)或多個(gè)指針引用(count字段大于或等于min_count字段)。

黑色節(jié)點(diǎn):表示此對象不需要被掃描(min_count字段等于 -1)。

接著我們來看看kmemleak內(nèi)核線程的實(shí)現(xiàn):

staticintkmemleak_scan_thread(void*arg)
{
...
while(!kthread_should_stop()){
...
kmemleak_scan();//進(jìn)行內(nèi)存泄漏掃描
...
}
return0;
}

可以看出kmemleak內(nèi)核線程主要通過調(diào)用kmemleak_scan函數(shù)來進(jìn)行內(nèi)存泄漏掃描。我們繼續(xù)來看看kmemleak_scan函數(shù)的實(shí)現(xiàn):

staticvoidkmemleak_scan(void)
{
...
//1)將所有kmemleak_object對象的count字段置0,表示開始時(shí)全部是白色節(jié)點(diǎn)
list_for_each_entry_rcu(object,&object_list,object_list){
...
object->count=0;
...
}
...

//2)掃描數(shù)據(jù)段與未初始化數(shù)據(jù)段
scan_block(_sdata,_edata,NULL,1);
scan_block(__bss_start,__bss_stop,NULL,1);
...

//3)掃描所有內(nèi)存頁結(jié)構(gòu),這是由于內(nèi)存頁結(jié)構(gòu)也可能引用其他內(nèi)存塊
for_each_online_node(i){
...
for(pfn=start_pfn;pfn

由于kmemleak_scan函數(shù)的代碼比較長,所以我們對其進(jìn)行精簡。精簡后可以看出,kmemleak_scan函數(shù)主要完成 5 件事情:

將系統(tǒng)中所有kmemleak_object對象的count字段置 0,表示掃描開始時(shí),所有節(jié)點(diǎn)都是白色節(jié)點(diǎn)。

調(diào)用scan_block函數(shù)掃描數(shù)據(jù)段與未初始化數(shù)據(jù)段,因?yàn)檫@兩個(gè)區(qū)域可能存在指針。

掃描所有內(nèi)存頁結(jié)構(gòu),這是因?yàn)閮?nèi)存頁結(jié)構(gòu)可能會引用其他內(nèi)存塊,所以也要對其進(jìn)行掃描。

掃描所有進(jìn)程內(nèi)核棧,由于進(jìn)程內(nèi)核??赡艽嬖谥羔?,所以要對其進(jìn)行掃描。

掃描所有灰色節(jié)點(diǎn),由于灰色節(jié)點(diǎn)也可能存在指針,所以要對其進(jìn)行掃描。

掃描主要通過scan_block函數(shù)進(jìn)行,我們來看看scan_block函數(shù)的實(shí)現(xiàn):

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);

//對內(nèi)存區(qū)進(jìn)行掃描
for(ptr=start;ptrcount++;

//判斷當(dāng)前對象是否灰色節(jié)點(diǎn),如果是將其添加到灰色節(jié)點(diǎn)鏈表中
if(color_gray(object)){
list_add_tail(&object->gray_list,&gray_list);
...
continue;
}
...
}
}

scan_block函數(shù)主要完成以下幾個(gè)步驟:

遍歷內(nèi)存區(qū)所有指針。

查找指針?biāo)玫膬?nèi)存塊是否存在于紅黑樹中,如果不存在就跳過處理此對象。

如果kmemleak_object對象不是白色,說明已經(jīng)有指針引用此內(nèi)存塊,跳過處理此對象。

對kmemleak_object對象的count字段進(jìn)行加一操作,表示有指針引用此內(nèi)存塊。

判斷當(dāng)前kmemleak_object對象是否是灰色節(jié)點(diǎn)(count字段大于或等于min_count字段),如果是將其添加到灰色節(jié)點(diǎn)鏈表中。

掃描完畢后,所有白色的節(jié)點(diǎn)就是可能存在內(nèi)存泄漏的內(nèi)存塊。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Linux系統(tǒng)
    +關(guān)注

    關(guān)注

    4

    文章

    588

    瀏覽量

    27265
  • C++語言
    +關(guān)注

    關(guān)注

    0

    文章

    147

    瀏覽量

    6944
收藏 人收藏

    評論

    相關(guān)推薦

    Linux內(nèi)存泄漏檢測實(shí)現(xiàn)原理與實(shí)現(xiàn)

    在使用沒有垃圾回收的語言時(shí)(如 C/C++),可能由于忘記釋放內(nèi)存而導(dǎo)致內(nèi)存被耗盡,這叫 內(nèi)存泄漏。由于內(nèi)核也需要自己管理內(nèi)存,所以也可能出
    發(fā)表于 12-09 11:11 ?943次閱讀

    細(xì)說Linux內(nèi)存泄漏檢測實(shí)現(xiàn)原理與實(shí)現(xiàn)

    在使用沒有垃圾回收的語言時(shí)(如 C/C++),可能由于忘記釋放內(nèi)存而導(dǎo)致內(nèi)存被耗盡,這叫 內(nèi)存泄漏。由于內(nèi)核也需要自己管理內(nèi)存,所以也可能出
    發(fā)表于 07-03 09:22 ?442次閱讀
    細(xì)說<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏檢測</b><b class='flag-5'>實(shí)現(xiàn)</b>原理與<b class='flag-5'>實(shí)現(xiàn)</b>

    煤氣泄漏檢測系統(tǒng)!畢業(yè)設(shè)計(jì)

    煤氣泄漏檢測系統(tǒng)!畢業(yè)設(shè)計(jì),高手請幫忙!
    發(fā)表于 03-24 01:48

    如何去解決電信設(shè)備內(nèi)的泄漏檢測?

    基于電信設(shè)備內(nèi)液體泄漏檢測的光電液位傳感器用于昂貴和關(guān)鍵系統(tǒng)的低成本泄漏檢測解決方案
    發(fā)表于 02-23 06:34

    寫了一個(gè)內(nèi)存泄漏檢查工具

    嵌入式環(huán)境內(nèi)存泄漏檢查比較麻煩,valgrind比較適合于在pc上跑,嵌入式上首先移植就很麻煩,移植完了內(nèi)存比較小,跑起來也比較費(fèi)勁。所以手動寫了一個(gè)內(nèi)存
    發(fā)表于 12-17 08:25

    泄漏檢測儀校正與調(diào)整

    本文概述了泄漏檢測儀的基本結(jié)構(gòu),針對泄漏檢測儀出現(xiàn)“誤判”故障,在校正及修理調(diào)試時(shí)采取了相應(yīng)措施,恢復(fù)了泄漏檢測儀正常使用功能。
    發(fā)表于 01-14 15:29 ?13次下載

    泄漏檢測技術(shù)

    從割草機(jī)到咖啡機(jī),任何的流體處理設(shè)備都需要進(jìn)行泄漏檢測,從而為其投入市場做論證準(zhǔn)備。通常,應(yīng)用在樣機(jī)設(shè)計(jì)階段的泄漏檢測方法也是在大批量生產(chǎn)中用于檢測的最好方法
    發(fā)表于 01-23 12:04 ?13次下載

    泄漏檢測及定位原理

    泄漏檢測及定位原理 當(dāng)管 道 發(fā) 生泄漏時(shí),泄漏點(diǎn)處由于管道內(nèi)外的壓差,流體迅速消失,壓力下降。泄漏點(diǎn)兩邊的流體由于存在壓差而
    發(fā)表于 01-08 11:48 ?1827次閱讀
    <b class='flag-5'>泄漏檢測</b>及定位原理

    沼氣泄漏檢測電路

    沼氣泄漏檢測電路
    發(fā)表于 02-15 13:35 ?503次閱讀
    沼氣<b class='flag-5'>泄漏檢測</b>電路

    氨氣泄漏的危害_氨氣泄漏檢測儀怎么使用_氨氣泄漏檢測儀的使用方法

    氨氣泄漏檢測儀 氨氣泄漏檢測儀測量范圍:0-100ppm、0-400ppm,聲光報(bào)警,高防水防塵設(shè)計(jì),具有數(shù)據(jù)存儲功能,聲光報(bào)警。
    發(fā)表于 01-03 09:57 ?2705次閱讀

    嵌入式裝置內(nèi)存泄漏檢測系統(tǒng)設(shè)計(jì)

    ,極易出現(xiàn)應(yīng)用程序內(nèi)存泄漏。內(nèi)存泄漏按照發(fā)生的頻率可分為常發(fā)性、偶發(fā)性、一次性以及隱式內(nèi)存泄漏4
    發(fā)表于 04-26 14:35 ?3次下載
    嵌入式裝置<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏檢測</b>系統(tǒng)設(shè)計(jì)

    真空泄漏檢測儀的重要性和應(yīng)用

    真空泄漏檢測儀是一種強(qiáng)大的設(shè)備,它能夠檢測和定位系統(tǒng)或部件的微小泄漏。在許多行業(yè)中,包括汽車、航空航天、醫(yī)療設(shè)備和半導(dǎo)體等,這種設(shè)備都是必不可少的。下面我們將詳細(xì)討論真空泄漏檢測儀的重
    的頭像 發(fā)表于 08-15 09:52 ?876次閱讀

    基于C++代碼實(shí)現(xiàn)內(nèi)存泄漏檢測工具

    看到的一個(gè)文章,有人用一個(gè)很簡短的代碼實(shí)現(xiàn)內(nèi)存檢測工具,大家看看實(shí)用性如何?
    發(fā)表于 08-21 10:11 ?632次閱讀
    基于C++代碼<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏檢測</b>工具

    如何寫一個(gè)內(nèi)存泄漏檢測工具

    如何確定有內(nèi)存泄露問題,如何定位到內(nèi)存泄露位置,如何寫一個(gè)內(nèi)存泄漏檢測工具? 1:概述 內(nèi)存泄露本質(zhì):其實(shí)就是申請調(diào)用malloc/new,
    的頭像 發(fā)表于 11-11 16:19 ?746次閱讀

    如何檢測內(nèi)存泄漏

    檢測內(nèi)存泄漏是軟件開發(fā)過程中一項(xiàng)至關(guān)重要的任務(wù),它有助于識別和解決那些導(dǎo)致程序占用過多內(nèi)存資源,從而影響程序性能甚至導(dǎo)致程序崩潰的問題。以下將詳細(xì)闡述幾種常見的
    的頭像 發(fā)表于 07-30 11:50 ?916次閱讀