嵌入式培訓(xùn)內(nèi)容會涉及到堆(heap)和棧(stack),它們是嵌入式系統(tǒng)中非常重要的概念,特別是當(dāng)我們要進(jìn)行嵌入式系統(tǒng)程序開發(fā)時得深入去理解堆和棧的定義。
在嵌入式系統(tǒng)中,任務(wù)的棧通常都很小,可能也就幾K字節(jié)。在這種情況下,我們就應(yīng)當(dāng)盡可能不要將占用內(nèi)存大的變量分配在棧上,而是應(yīng)當(dāng)分配在堆上;此外,也盡量不要采用遞歸的方式來設(shè)計(jì)程序,否則很容易造成棧溢出。
從本質(zhì)上說,堆和棧都是內(nèi)存,那么我們只能從嵌入式培訓(xùn)內(nèi)容概念上對其進(jìn)行區(qū)分了。為了方便說明,現(xiàn)在假設(shè)嵌入式軟件是一個單體程序和我們的應(yīng)用程序是被編譯在同一個可執(zhí)行程序當(dāng)中的,比如,來自WindRiver的VxWorks就是采用這種方式的。我們知道一個可執(zhí)行程序存在為重要的三個段。.text段用于存放程序的代碼,即放的是處理器的運(yùn)行指令。.data用于存放初始化好的數(shù)據(jù),當(dāng)boot loader加載程序文件時,會將程序文件中的.data段拷貝到內(nèi)存的VMA(Virtual Memory Address)處,從而完成變量的初始化操作。雖然,我們在C/C++程序中是對全局變量一個一個初始化的,但實(shí)際上boot loader是對所有的全局變量通過將程序文件中的.data段拷貝到內(nèi)存中一次性的完成初始化的。.bss段用于存放沒有初始化好的變量,程序文件中并不存放.bss段的具體內(nèi)容,只是存有.bss段的起始地址和大小,當(dāng)boot loader加載我們的嵌入式程序文件時,只是根據(jù)程序文件中的.bss信息對內(nèi)存中的.bss塊進(jìn)行清零操作。
堆我們說過了,那接下來我們看一看如果我們的單體程序繼續(xù)運(yùn)行,會出現(xiàn)什么樣的內(nèi)存布局。我們知道,通常我們的單體程序在初始化時往往需要創(chuàng)建多個任務(wù)來實(shí)現(xiàn)其應(yīng)用功能。對于每一個任務(wù),它一塊內(nèi)存是私有的,那就是棧!當(dāng)任務(wù)運(yùn)行時,其需要用棧來做為函數(shù)調(diào)用時的參數(shù)傳遞空間,以及用棧來存儲函數(shù)內(nèi)的局部變量。假設(shè)我們的單體程序需要創(chuàng)建兩個任務(wù)A和B,這需要通過調(diào)用操作系統(tǒng)中的任務(wù)創(chuàng)建函數(shù)來達(dá)到這一目的。操作系統(tǒng)所提供的任務(wù)創(chuàng)建API往往需要我們指定任務(wù)棧的大小,有的甚至可以指定棧內(nèi)存空間。一旦任務(wù)創(chuàng)建的API被調(diào)用,那么操作系統(tǒng)會調(diào)用堆分配API為任務(wù)分配棧。
對于堆我們已經(jīng)知道了必須調(diào)用相應(yīng)的API來分配內(nèi)存,那從棧空間分配內(nèi)存也需要調(diào)用API嗎?答案是通常不需要,為什么是通常?因?yàn)椋谟械钠脚_上(Linux上就是)提供棧空間的分配API,即這種API被調(diào)用時,是從調(diào)用任務(wù)的棧空間中分配內(nèi)存的。對于這一功能,在嵌入式系統(tǒng)中使用得非常的少,粵嵌嵌入式培訓(xùn)不建議大家使用。對于下面的代碼,mem_main、mem_foo和mem_bar的大小是4K字節(jié)(假設(shè)int類型的大小是4字節(jié)),這些內(nèi)存就是自動(注意是自動)分配在運(yùn)行任務(wù)的棧上的。我們假設(shè)某個任務(wù)當(dāng)前所使用的棧是零字節(jié),當(dāng)這一任務(wù)運(yùn)行到main中且沒有進(jìn)入foo ()時,其所占用的空間大小是大約4K字節(jié),之所以用大約這個詞,是因?yàn)楹瘮?shù)的調(diào)用還有其它的棧開銷。一旦任務(wù)運(yùn)行進(jìn)入foo ()函數(shù)但沒有進(jìn)入bar ()函數(shù),那么所占用的棧的大小就變?yōu)榇蠹s8K字節(jié)。同樣的,如果程序運(yùn)行進(jìn)入bar ()函數(shù),那么所占用的棧空間大約就是12K字節(jié)了。
void bar ()
{
int mem_bar [1024];
// application logic
}
void foo ()
{
int mem_foo [1024];
bar ();
}
int main ()
{
int mem_main [1024];
foo ();
return 0;
}
如果程序繼續(xù)運(yùn)行,從bar ()函數(shù)返回到foo ()函數(shù)中,那么其所占用的棧空間就從大約12K字節(jié)變成了大約8K字節(jié)了。相類似的是,如果程序從foo ()函數(shù)中返回到main ()函數(shù),那么所占用的棧空間又變?yōu)榇蠹s4K字節(jié)了。對于嵌入式系統(tǒng)開發(fā),由于任務(wù)棧通常都比較的小,那這告訴我們什么呢?我想有以下幾點(diǎn)需要注意。
1)函數(shù)的調(diào)用深度越是深,由于每一級的函數(shù)通常都會有局部變量,那么所使用的棧空間也會累積得越大。
2)遞歸調(diào)用需要的棧空間會相對的大(視具體的情況),在嵌入式系統(tǒng)中也建議少用。
3)我們應(yīng)當(dāng)盡可能的不要在函數(shù)中定義占用內(nèi)存空間較大的局部變量。
下面,我們總結(jié)一下堆與棧的區(qū)別,它們是:
1)堆是大家共享的。任務(wù)可以通過調(diào)用API來從堆中分配內(nèi)存空間。
2)棧是任務(wù)所獨(dú)有的。在嵌入式系統(tǒng)開發(fā)中,當(dāng)一個任務(wù)創(chuàng)建起來后其棧空間的大小往往是定了的。函數(shù)中的局部變量是由編程語言自動從棧上分配的,我們不需要調(diào)用API進(jìn)行空間分配。
關(guān)于嵌入式培訓(xùn)內(nèi)容中堆和棧的理解就先說到這里,如果你對嵌入式感興趣,到粵嵌培訓(xùn)會是一條不錯的出路。