這一篇,我們將深入討論內(nèi)存管理,所謂的內(nèi)存管理其實(shí)就是動(dòng)態(tài)內(nèi)存的管理,只有動(dòng)態(tài)內(nèi)存才會(huì)有生命周期,又開(kāi)始有結(jié)束,他才會(huì)有需要管理的問(wèn)題,例如我們?nèi)祟悾偃缡庆o態(tài)的內(nèi)存,從被定義開(kāi)始,他就是永恒存在的,不需要管理。
在C語(yǔ)言中,經(jīng)常使用malloc()和free()函數(shù)來(lái)動(dòng)態(tài)的申請(qǐng)和釋放內(nèi)存,這種方式是基于堆來(lái)管理內(nèi)存的方式,這種方式在嵌入式實(shí)時(shí)系統(tǒng)中,會(huì)存在很多問(wèn)題隱患:
1.頻繁的使用這種方式進(jìn)行內(nèi)存的分配和釋放,會(huì)把堆搞得支離破碎,并因?yàn)椴荒茉俜峙涓嗟膬?nèi)存而導(dǎo)致應(yīng)用程序崩潰。
2.基于堆得內(nèi)存管理是浪費(fèi)的,所有的堆管理算法必須為每個(gè)被分配的塊維護(hù)某些頭部信息,造成內(nèi)存額外的開(kāi)銷。
3.malloc()和free()函數(shù)在實(shí)際申請(qǐng)和釋放內(nèi)存的時(shí)候,執(zhí)行的時(shí)間是不可確定的,這意味著他們潛在的可能需要一段長(zhǎng)的時(shí)間,這與我們的實(shí)時(shí)原則是矛盾的。
然而,堆的問(wèn)題并不僅僅限于以上這些,在多線程環(huán)境中使用堆時(shí),會(huì)引入新的問(wèn)題,首先堆變成了一個(gè)共享資源,這引入了并發(fā)的問(wèn)題:
1. malloc()和free()函數(shù)是不可重入的,也就是說(shuō),他們不能從多個(gè)線程被安全的調(diào)用。當(dāng)然可以通過(guò)互斥體來(lái)彌補(bǔ)API無(wú)法重入的問(wèn)題,但是涉及到互斥體,你又該考慮線程阻塞的問(wèn)題以及由于互斥體的加入,是否會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題,問(wèn)題似乎變得更復(fù)雜了。
2.malloc()和free()是必須成雙入對(duì),當(dāng)你需要使用時(shí),申請(qǐng)了一塊內(nèi)存,當(dāng)你使用完該內(nèi)存時(shí),一定要主動(dòng)地去釋放他,不然它會(huì)一直占用該內(nèi)存,變成了靜態(tài)內(nèi)存的存在,相當(dāng)于在動(dòng)態(tài)內(nèi)存里面開(kāi)了一塊靜態(tài)的內(nèi)存,當(dāng)你只開(kāi)一塊或者幾塊的時(shí)候,也沒(méi)問(wèn)題,怕就怕,某種情況下觸發(fā)了頻繁這樣的去申請(qǐng)內(nèi)存,而在使用完時(shí),又沒(méi)有釋放,積少成多,你的堆就被消耗干凈了。
3.與第二種方式相反,當(dāng)你申請(qǐng)了一塊內(nèi)存以后,在應(yīng)用程序還沒(méi)有使用完的時(shí)候,被意外釋放了,那么將會(huì)造成dangling指針,當(dāng)應(yīng)用程序再次使用該指針時(shí)你的應(yīng)用程序可能崩潰。
4.堆相關(guān)的問(wèn)題是出名的難以測(cè)試。
講了這么多堆的缺點(diǎn),堆有個(gè)最大的優(yōu)點(diǎn),可以申請(qǐng)可變大小的內(nèi)存塊,這是我們接下里介紹的內(nèi)存池的方式無(wú)法提供的,凡是總是有利有弊,引入內(nèi)存池是為了解決以上由于使用堆而帶來(lái)的那些問(wèn)題。
為了更簡(jiǎn)單,更高性能和更安全的使用動(dòng)態(tài)內(nèi)存,一個(gè)通用的方案是,定義一塊尺寸固定的堆,也被稱為內(nèi)存池,對(duì)于QF框架來(lái)說(shuō),在管理動(dòng)態(tài)事件時(shí),使用內(nèi)存池可能是一個(gè)更好的選擇。
先來(lái)看看使用內(nèi)存池的缺點(diǎn),首先他只能以一種固定尺寸的塊,但是事件的尺寸并不是統(tǒng)一的,所以為了支持所有的事件,塊的尺寸必須被定義為能夠支持的最大尺寸的事件,這樣會(huì)造成內(nèi)存的浪費(fèi),折中的方案是定義多個(gè)不同尺寸的塊的內(nèi)存池,用于支持不同尺寸的事件,QF最大支持三種不同尺寸的內(nèi)存池,你可以定義小中大三種。
接下里說(shuō)說(shuō)他的優(yōu)點(diǎn):
1.內(nèi)尺寸不會(huì)因?yàn)轭l繁的申請(qǐng)和釋放變得碎片化。
2.申請(qǐng)的速度遠(yuǎn)快于堆申請(qǐng)速度,僅需臨界區(qū)操作,避免的阻塞的情況。
3.動(dòng)態(tài)的事件管理完全由QF框架掌控,他自身就有垃圾回收機(jī)制,類似java,所以只管申請(qǐng),不用擔(dān)心釋放問(wèn)題。
事件的管理完全交給了QF框架,那么QF需要知道以下幾件事:
1.這個(gè)事件需不需要我管理(動(dòng)態(tài)還是靜態(tài)事件)。
2.這個(gè)事件來(lái)自于哪個(gè)內(nèi)存池。
3.這個(gè)事件在什么情況下被釋放(釋放條件)。 以上這些內(nèi)容在事件被定義時(shí)就被確認(rèn)了,也就是由事件本身來(lái)維護(hù),如圖:
關(guān)于內(nèi)存池的使用有兩種情況,一種是當(dāng)QF框架和其它RTOS一起使用時(shí),他可以借用RTOS現(xiàn)有的消息隊(duì)列,這種情況后面講到QP+RTOS方案時(shí),我們具體再聊,這里主要介紹一種原生的QF事件隊(duì)列,所謂原生就是由QF給出的解決方案。 首先我們來(lái)看一下QF是如何管理事件池的,其控制方案由一個(gè)控制塊+內(nèi)存池的方案組成:
接下來(lái)我們看一下如何實(shí)際定義一個(gè)內(nèi)存池及如何從內(nèi)存池中申請(qǐng)內(nèi)存:
1.先定義內(nèi)存池。
2.初始化內(nèi)存池。
3.為事件申請(qǐng)內(nèi)存
動(dòng)態(tài)事件申請(qǐng)內(nèi)存不需要釋放,QF框架會(huì)根據(jù)實(shí)際情況來(lái)回收事件內(nèi)存。