姓名:朱小鵬 ? 學(xué)號(hào):16010130023
轉(zhuǎn)載:http://blog.sina.com.cn/s/blog_62a85b950101am7f.html
【嵌牛導(dǎo)讀】:昨天說(shuō)到了數(shù)據(jù)緩沖pbuf的內(nèi)存申請(qǐng),今天繼續(xù)來(lái)探究一下它的內(nèi)存釋放過(guò)程。
【嵌牛鼻子】:pbuf釋放
【嵌牛提問(wèn)】:LWIP中內(nèi)存釋放過(guò)程如何實(shí)現(xiàn)?
【嵌牛正文】:
不停的寫(xiě)東西,只是為了逃避某種心情。(PS:現(xiàn)在不停的C+V,只是迫于某人的鴨梨和對(duì)知識(shí)傳播的追求,霍霍)
牢騷發(fā)完,Go On。昨天說(shuō)到了數(shù)據(jù)緩沖pbuf的內(nèi)存申請(qǐng),今天繼續(xù)來(lái)探究一下它的內(nèi)存釋放過(guò)程。由于pbuf的申請(qǐng)主要是通過(guò)內(nèi)存堆分配和內(nèi)存池分配來(lái)實(shí)現(xiàn),所以,pbuf的釋放也必須按照這兩種情況分別討論。
別慌,在展開(kāi)討論之前,還得說(shuō)說(shuō)某個(gè)pbuf能被釋放的前提。在LWIP中這點(diǎn)很容易判斷,因?yàn)榍肮?jié)說(shuō)到pbuf的ref字段表示該pbuf被引用的次數(shù),當(dāng)pbuf被創(chuàng)建時(shí),該字段的初始值為1,由此可判斷,當(dāng)pbuf的ref字段為1時(shí),該pbuf才可以被刪除,所以位于pbufs鏈表中間的pbuf結(jié)構(gòu)是不會(huì)被刪除成功的,因?yàn)樗麄兊膔ef值至少是2。由此總之一下,能被刪除的pbuf必然是某個(gè)pbufs鏈的首節(jié)點(diǎn)。當(dāng)然pbuf的刪除工作遠(yuǎn)不如此的簡(jiǎn)單,其中另一個(gè)需要特別注意的地方是,想想,很可能的情況是某個(gè)pbufs鏈的首節(jié)點(diǎn)刪除成功后,該pbufs鏈的第二個(gè)節(jié)點(diǎn)就自然的成為該pbufs鏈的首節(jié)點(diǎn),此時(shí),該節(jié)點(diǎn)的ref值可能變?yōu)?(該節(jié)點(diǎn)沒(méi)有被引用了),這種情況下,該節(jié)點(diǎn)也會(huì)被刪除,因?yàn)長(zhǎng)WIP認(rèn)為它和第一個(gè)節(jié)點(diǎn)一起存儲(chǔ)同一數(shù)據(jù)包。當(dāng)?shù)诙€(gè)節(jié)點(diǎn)也被刪除后,LWIP又會(huì)去看看第三個(gè)節(jié)點(diǎn)是否滿足刪除條件…就這樣一直刪一下去。當(dāng)然,如果首節(jié)點(diǎn)刪除后,第二個(gè)節(jié)點(diǎn)的ref值大于1,表示該節(jié)點(diǎn)還在其他地方被引用,不能再被刪除,刪除工作至此結(jié)束。這段話寫(xiě)的很口水,不如我們舉個(gè)例子來(lái)看看這個(gè)刪除過(guò)程。假如現(xiàn)在我們的pbufs鏈表有A,B,C三個(gè)pbuf結(jié)構(gòu)連接起來(lái),結(jié)構(gòu)為A--->B--->C,利用pbuf_free(A)函數(shù)來(lái)刪除pbuf結(jié)構(gòu),下面用ABC的幾組不同ref值來(lái)看看刪除結(jié)果:
(1)1->2->3函數(shù)執(zhí)行后變?yōu)?..1->3,節(jié)點(diǎn)BC仍在;
(2)3->3->3函數(shù)執(zhí)行后變?yōu)?->3->3,節(jié)點(diǎn)ABC仍在;
(3)1->1->2函數(shù)執(zhí)行后變?yōu)?.....1,節(jié)點(diǎn)C仍在;
(4)2->1->1函數(shù)執(zhí)行后變?yōu)?->1->1,節(jié)點(diǎn)ABC仍在;
(5)1->1->1函數(shù)執(zhí)行后變?yōu)?......,節(jié)點(diǎn)全部被刪除。
如果您能說(shuō)醍醐灌頂,那將是我最大的動(dòng)力。
當(dāng)可以刪除某個(gè)pbuf結(jié)構(gòu)時(shí),LWIP首先檢查這個(gè)pbuf是屬于前節(jié)講到的四個(gè)類(lèi)型中的哪種,根據(jù)類(lèi)型的不同,調(diào)用不同的內(nèi)存釋放函數(shù)進(jìn)行刪除。PBUF_POOL類(lèi)型和PBUF_ROM類(lèi)型、PBUF_REF類(lèi)型需要通過(guò)memp_free()函數(shù)刪除,PBUF_RAM類(lèi)型需要通過(guò)mem_free()函數(shù)刪除,原因不解釋。
PBUF_RAM類(lèi)型來(lái)自于內(nèi)存堆,所以需通過(guò)mem_free()函數(shù)將pbuf釋放回內(nèi)存堆。這里,先得來(lái)看看內(nèi)存堆的組織結(jié)構(gòu),見(jiàn)下圖,在內(nèi)存堆內(nèi)部,內(nèi)存堆管理模塊通過(guò)在每一個(gè)內(nèi)存分配塊的頂部放置一個(gè)比較小的結(jié)構(gòu)體來(lái)保存內(nèi)存分配紀(jì)錄(注意這個(gè)小小的結(jié)構(gòu)體是內(nèi)存管理模塊自動(dòng)附加上去的,獨(dú)立于用戶(hù)的申請(qǐng)大小)。這個(gè)結(jié)構(gòu)體擁有三個(gè)成員變量,兩個(gè)指針和一個(gè)標(biāo)志,如圖。next與prev分別指向內(nèi)存的下一個(gè)和上一個(gè)分配塊,used標(biāo)志表示該內(nèi)存塊是否已被分配。圖中需要注意的兩個(gè)地方,first,圖中每個(gè)內(nèi)存塊的大小是不同且可能隨時(shí)變化的。Second,當(dāng)系統(tǒng)初始化的時(shí)候,整個(gè)內(nèi)存堆就是一個(gè)內(nèi)存塊,下圖中是經(jīng)過(guò)多次分配釋放后內(nèi)存堆呈現(xiàn)出來(lái)的結(jié)果。
內(nèi)存堆管理模塊根據(jù)所申請(qǐng)分配的大小來(lái)搜索所有未被使用的內(nèi)存分配塊,檢索到的最先滿足條件的內(nèi)存塊將分配給申請(qǐng)者,注意這里并不包括前面說(shuō)到的那個(gè)小小結(jié)構(gòu)體,所以用戶(hù)得到的是used后的那個(gè)地址。當(dāng)分配成功后,內(nèi)存管理模塊會(huì)馬上在已經(jīng)分配走了的數(shù)據(jù)區(qū)后面再插一個(gè)小小的結(jié)構(gòu)體,并用next和prev指針將這個(gè)結(jié)構(gòu)體串起來(lái),以便于下次分配。經(jīng)過(guò)幾次的申請(qǐng)與釋放,我們就看到了圖中的內(nèi)存堆組織模型。
當(dāng)內(nèi)存釋放的時(shí)候,為了防止內(nèi)存碎片的產(chǎn)生,上一個(gè)與下一個(gè)分配塊的使用標(biāo)志會(huì)被檢查,如果他們中的任何一個(gè)還未被使用,這個(gè)內(nèi)存塊將被合并到一個(gè)更大的未使用內(nèi)存塊中。內(nèi)存堆管理模塊是這樣做的,它根據(jù)用戶(hù)提供的釋放地址退后幾個(gè)字節(jié)去尋找這個(gè)小小的結(jié)構(gòu)體,利用這個(gè)結(jié)構(gòu)體來(lái)實(shí)現(xiàn)內(nèi)存堆得合并等操作。已經(jīng)分配的內(nèi)存塊被回收后,使用標(biāo)志used清零。當(dāng)然,如果上一個(gè)與下一個(gè)分配塊都已被使用,這時(shí)的釋放就是最簡(jiǎn)單的情況,但這也是產(chǎn)生內(nèi)存堆碎片問(wèn)題的根源。
哎,要了老命了。
接著來(lái)講其他三種結(jié)構(gòu)通過(guò)memp_free()函數(shù)將pbuf釋放回內(nèi)存池的情況。前面的內(nèi)容已經(jīng)講過(guò)了內(nèi)存池POOL的結(jié)構(gòu),PBUF_POOL型pbuf主要使用的是MEMP_PBUF_POOL類(lèi)型的POOL,PBUF_ROM和PBUF_REF型pbuf主要使用的是MEMP_PBUF型的POOL。這句話太繞了,你應(yīng)該多讀兩遍。POOL結(jié)構(gòu)的起始處有個(gè)next指針,用于指向同類(lèi)型的下一個(gè)POOL,用于將同類(lèi)型的POOL連接成一個(gè)單向鏈表,這里應(yīng)該有必要仔細(xì)看看POOL池是怎樣初始化的,代碼很簡(jiǎn)單:
memp = LWIP_MEM_ALIGN(memp_memory);
for (i = 0; i < MEMP_MAX; ++i) {//對(duì)各種類(lèi)型的POOL依次操作
memp_tab[i] = NULL;//空閑鏈表頭初始為空
for (j = 0; j < memp_num[i]; ++j) {//把同類(lèi)POOL鏈成鏈表
memp->next = memp_tab[i];
memp_tab[i] = memp;
memp = (struct memp *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]);//取得下
}//一個(gè)POOL的地址
}
上面代碼中有幾個(gè)重要的全局變量,memp_memory是緩沖池的起始地址,前面已有所討論;MEMP_MAX是POOL類(lèi)型數(shù); memp_tab用于指向某類(lèi)POOL空閑鏈表的起始節(jié)點(diǎn);memp_num表示各種類(lèi)型POOL的個(gè)數(shù);memp_sizes表示各種類(lèi)型單個(gè)POOL的大小,對(duì)于MEMP_PBUF_POOL和MEMP_PBUF型的POOL,其大小是pbuf頭和pbuf可裝載數(shù)據(jù)大小的總和。
在這樣的基礎(chǔ)之上,POOL池的釋放就簡(jiǎn)單了,首先根據(jù)POOL的類(lèi)型找到相應(yīng)空閑鏈表頭memp_tab,將該P(yáng)OOL插在鏈表頭上,并把memp_tab指向鏈表頭,簡(jiǎn)單快捷。至于POOL池的的申請(qǐng)那自然而然的也就是對(duì)memp_tab頭的操作了,這個(gè)相信你懂。
好了,到這里,LWIP的內(nèi)存相關(guān)機(jī)制就基本介紹完畢。當(dāng)然,它不可能這樣簡(jiǎn)單,在以后使用到的地方,我會(huì)再加說(shuō)明。
整理整理,收工。周末來(lái)啦….冬眠一天是必不可少的….哈哈