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