教你輕松控制uClinux 嵌入式開發(fā)過程
恰當(dāng)?shù)膬?nèi)存分配
uClinux除了提供跟普通Linux一樣的內(nèi)存分配器之外,還提供另一個可選的。普通Linux中缺省的內(nèi)存分配器是使用“2的冪”的分配方法,這樣可以快速找到符合要求的內(nèi)存區(qū)域。不幸的是,在uClinux下這種方法可能會帶來令人痛苦的結(jié)果。
為了理解這一問題帶來的結(jié)果,尤其是大的內(nèi)存分配,我們舉例說明。試想一個應(yīng)用程序要求33KB的內(nèi)存空間進(jìn)行裝載。如果使用“2的冪”的分配方法,就必須分配64KB(2的6次方)內(nèi)存空間,多余的31KB內(nèi)存空間不能被利用上。在uClinux中,這種浪費是不能接受的。為了解決這個問題,專門為 uClinux內(nèi)核設(shè)計了可選的內(nèi)存分配器。不同的內(nèi)核版本,這個可選的內(nèi)存分配器不同,一般是page_alloc2和kmalloc2。
page_alloc2能解決缺省的分配方法造成的浪費問題。雖然它也是使用“2的冪”的分配方法,但它是按頁(每頁4096字節(jié),即4KB)分配的,分配的內(nèi)存大小如果已經(jīng)滿足了要求,則只是將當(dāng)前的一頁分配出去,其它的就不再分配。在前面的例子中,如果使用這種方法,就只是分配36KB (≥33KB,且為整頁)即可,這樣就能節(jié)省28KB的空間。
page_alloc2還采取了一些避免內(nèi)存碎片的方法。它將所有的兩頁(8KB)或更少的內(nèi)存需求從空閑內(nèi)存開始部分向上分配,所有大的內(nèi)存需求從剩余內(nèi)存的末尾部分開始向下分配。這樣防止了網(wǎng)絡(luò)緩存等的臨時分配,避免了內(nèi)存碎片的出現(xiàn)。
一旦開發(fā)者理解了內(nèi)核內(nèi)存分配的區(qū)別,應(yīng)用程序中就會出現(xiàn)變化。
1.沒有動態(tài)棧的問題
在使用虛擬內(nèi)存的Linux上,當(dāng)一個應(yīng)用程序試圖沖銷棧頂單元時,會被標(biāo)記異常,同時系統(tǒng)會映射新的內(nèi)存到棧頂以便讓棧增長。在 uClinux下,由于必須在編譯階段給棧分配好內(nèi)存,所以不會有這樣的增長。當(dāng)出現(xiàn)莫名其妙的崩潰或者新移植的應(yīng)用程序出現(xiàn)怪異行為時,開發(fā)者首先應(yīng)該考慮到的是給棧分配的內(nèi)存大小問題。缺省情況下,uClinux為棧分配4KB的內(nèi)存空間,開發(fā)者可以用下面提到的方法之一來增加棧的空間。
◆ 應(yīng)用程序build之前
應(yīng)用程序build之前,可以在Makefile文件中增加以下兩行代碼:
FLTFLAGS = -s
export FLTFLAGS
◆ 應(yīng)用程序build之后
應(yīng)用程序build之后,可以運行以下命令:
flthdr -s executable
其中,stacksize 就是為棧增加的內(nèi)存空間。
2.沒有動態(tài)堆的問題
堆是C語言中malloc及相關(guān)函數(shù)分配內(nèi)存的區(qū)域。在有虛擬內(nèi)存的Linux上,應(yīng)用程序可能通過動態(tài)堆在運行過程中改變進(jìn)程的大小。這個功能是通過在底層使用sbrk()和brk()系統(tǒng)調(diào)用來實現(xiàn)的。sbrk()是在進(jìn)程的末尾增加內(nèi)存空間,所以調(diào)用sbrk()能夠使應(yīng)用程序獲得額外的內(nèi)存。
brk()可以把任意位置設(shè)置為進(jìn)程空間的末尾,因此,可以通過調(diào)用brk()減少或增加內(nèi)存空間的占用。由于uClinux不能實現(xiàn)brk()和sbrk(),它采用了一個全局的內(nèi)存池,就是內(nèi)核的空閑內(nèi)存池。使用全局內(nèi)存池的方法有一些優(yōu)點。
首先,此方法只會給進(jìn)程分配使用時真正需要的內(nèi)存。其次,內(nèi)存用完后就會被歸還給全局內(nèi)存池,而且可以利用已經(jīng)存在的內(nèi)核中的分配器來分配內(nèi)存,這樣可以減少應(yīng)用程序的代碼量。但這個方法是有缺陷的,比如,一個失控的進(jìn)程可以用完系統(tǒng)全部的可用內(nèi)存。
新手普遍會遇到丟失內(nèi)存的問題。系統(tǒng)會顯示大量的可用內(nèi)存,但是應(yīng)用程序卻不能得到。這正是由于內(nèi)存碎片的存在,uClinux幾乎不可能完全利用內(nèi)存,現(xiàn)有的解決方法中都存在這個問題。這個問題可用一個例子很好地說明。
假設(shè)一個系統(tǒng)有500KB的空閑內(nèi)存,為了裝載一個應(yīng)用程序需要分配100KB的空間。大家可能覺得這個需要肯定能得到滿足,然而,應(yīng)該知道,必須有 100KB連續(xù)的內(nèi)存空間才能滿足這個需要。如果有500KB的空閑空間,但是最大的連續(xù)內(nèi)存塊的大小只有80KB,這樣是沒有辦法分配給這個應(yīng)用程序的。造成這種情況有很多原因。上面講到的page_alloc2內(nèi)核分配器有一個配置選項可以用來識別這個問題,在內(nèi)核源代碼page_alloc2.c 文件中可以獲得更多的信息。
經(jīng)常有人會問為什么不能進(jìn)行內(nèi)存的碎片整理,以便實現(xiàn)剛才的例子中的要求?原因是uClinux沒有虛擬內(nèi)存,所以不能移動程序正在使用的內(nèi)存。在使用虛擬內(nèi)存的情況下,只要重新定位就能實現(xiàn)內(nèi)存的移動,從而實現(xiàn)內(nèi)存碎片的整理。
在沒有虛擬內(nèi)存的情況下,由于程序經(jīng)常會引用已經(jīng)分配給它的內(nèi)存區(qū)域,這樣,如果移動程序的內(nèi)存,程序就會崩潰。在uClinux下,現(xiàn)在還沒有解決這個問題的辦法。開發(fā)者需要自己注意這個問題,如果有可能的話,盡量使用小的內(nèi)存塊。
掌控進(jìn)程和應(yīng)用程序
1.進(jìn)程
有虛擬內(nèi)存的Linux和uClinux的另一個區(qū)別在于后者沒有fork()系統(tǒng)調(diào)用。這就要求開發(fā)者在移植時對使用了fork()的應(yīng)用程序做一些工作。uClinux下惟一的選擇是使用vfork()。盡管vfork()與fork()有很多共同點,但是它們之間的區(qū)別影響很大。
對于不熟悉fork()和vfork()的人來說,這兩個系統(tǒng)調(diào)用都是允許將一個進(jìn)程分裂成一個父進(jìn)程和一個子進(jìn)程。當(dāng)一個進(jìn)程調(diào)用 fork()時,子進(jìn)程是父進(jìn)程的一個完全拷貝,但是它不共享父進(jìn)程的任何東西,并且能夠單獨執(zhí)行,就和父進(jìn)程一樣。vfork()調(diào)用就不同了,首先,父進(jìn)程被掛起直到子進(jìn)程調(diào)用exec(),或者子進(jìn)程退出才能繼續(xù)。
由此可見,這個系統(tǒng)調(diào)用是用來啟動一個新的應(yīng)用程序。其次,子進(jìn)程在vfork()返回后直接運行在父進(jìn)程的??臻g,并使用父進(jìn)程的內(nèi)存和數(shù)據(jù)。這意味著子進(jìn)程可能破壞父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)或棧,造成失敗。
為了避免這些問題,需要確保一旦調(diào)用vfork(),子進(jìn)程就不從當(dāng)前的棧框架中返回,并且如果子進(jìn)程改變了父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)就不能調(diào)用exit函數(shù)。子進(jìn)程還必須避免改變?nèi)謹(jǐn)?shù)據(jù)結(jié)構(gòu)或全局變量中的任何信息,因為這些改變都有可能使父進(jìn)程不能繼續(xù)。
評論