使用ADS1.2進(jìn)行嵌入式軟件開發(fā)(下)
放置堆棧和heap
Scatterloading機(jī)制提供了一種指定代碼和靜態(tài)數(shù)據(jù)布局的方法。下面介紹如何放置應(yīng)用程序的堆棧和heap。
* _user_initial_stackheap重定向
應(yīng)用程序的堆棧和heap是在C庫函數(shù)初始化過程中建立起來的。可以通過重定向?qū)?yīng)的子程序來改變堆棧和heap的位置,在ADS的庫函數(shù)中,即_user_initial_stackheap()函數(shù)。
_user_initial_stackheap()可以用C或匯編來實(shí)現(xiàn),它必須返回如下參數(shù):
r0:heap基地址;
r1:堆棧基地址;
r2:heap長(zhǎng)度限制值(需要的話);
r3:堆棧長(zhǎng)度限制值。
當(dāng)用戶使用分散裝載功能的時(shí)候,必須重調(diào)用_user_initial_stackheap(),否則連接器會(huì)報(bào)錯(cuò):
Error: L6218E: Undefined symbol Image$$ZI$$Limit (referred from sys_stackheap.o)
*存儲(chǔ)器模型
ADS提供了兩種實(shí)時(shí)存儲(chǔ)器模型。缺省時(shí)為one-region,應(yīng)用程序的堆棧和heap位于同一個(gè)存儲(chǔ)器區(qū)塊,使用的時(shí)候相向生長(zhǎng),當(dāng)在heap區(qū)分配一塊存儲(chǔ)器空間時(shí)需要檢查堆棧指針。另一種情況是堆棧和heap使用兩塊獨(dú)立的存儲(chǔ)器區(qū)域。對(duì)于速度特別快的RAM,可選擇只用來作堆棧使用。為了使用這種two-region模型,用戶需要導(dǎo)入符號(hào)use_two_region_memory,heap使用需要檢查heap的長(zhǎng)度限制值。
對(duì)這兩種模型來說,缺省情況下對(duì)堆棧的生長(zhǎng)都不進(jìn)行檢查。用戶可以在程序編譯時(shí)使用 -apcs/swst 編譯器選項(xiàng)來進(jìn)行軟件堆棧檢查。如果使用two-region模型,必須得在執(zhí)行_user_initial_stackheap時(shí)指定一個(gè)堆棧限制值。
圖9 重定向_user_initial_stackheap()
圖10 基本初始化過程
圖11 ROM/RAM重定向和映射
表1
系統(tǒng)復(fù)位和初始化
目前情況,一般假設(shè)程序從C庫函數(shù)的初始化入口_main開始執(zhí)行。實(shí)際上,所有的嵌入式程序在啟動(dòng)時(shí)都要執(zhí)行一些系統(tǒng)級(jí)的初始化操作。在此討論這方面的內(nèi)容。
初始化過程
圖10中顯示了一個(gè)基于ARM的嵌入式系統(tǒng)的基本初始化過程??梢钥吹剑赺main之前加入了一個(gè)復(fù)位處理模塊reset handler,它在系統(tǒng)上電復(fù)位時(shí)立即啟動(dòng)。標(biāo)識(shí)為$sub$$main的新代碼塊在進(jìn)入主程序之前執(zhí)行。
復(fù)位處理模塊reset handler通常是一小段匯編代碼,在系統(tǒng)復(fù)位時(shí)執(zhí)行。它至少完成應(yīng)用程序中使用到的所有處理器模式的堆棧初始化工作。對(duì)于含有本地存儲(chǔ)器系統(tǒng)的內(nèi)核(比如含cache的ARM內(nèi)核),配置工作也必須在這一段初始化過程中完成。當(dāng)完成系統(tǒng)初始化之后,通常程序會(huì)跳向_main,開始C庫函數(shù)的初始化過程。
系統(tǒng)初始化過程一般還包括另外一些內(nèi)容,中斷使能等,這些大多安排在C庫函數(shù)的初始化完成之后執(zhí)行。$sub$$main()完成這部分功能。
向量表(vector table)
所有的ARM系統(tǒng)都有一張中斷向量表當(dāng)出現(xiàn)異常需要處理時(shí),必須調(diào)用向量表。向量表一般要位于0地址處。
表2
表3
表4
表5
表6
表7
表8
表9
表10
存儲(chǔ)器配置
*ROM/RAM重定向
當(dāng)系統(tǒng)啟動(dòng)的時(shí)候,為了保證0地址處有正確的啟動(dòng)代碼存在,需要非易失性的存儲(chǔ)器。
一種簡(jiǎn)單的方法,就是把系統(tǒng)0x0000開始的一塊地址分配給ROM。其缺點(diǎn)是,由于ROM的訪問速度比RAM慢很多,當(dāng)執(zhí)行中斷響應(yīng)需要從中斷向量表跳轉(zhuǎn)時(shí),會(huì)給系統(tǒng)性能帶來損失;同時(shí),在ROM中的向量表內(nèi)容也不能被用戶程序動(dòng)態(tài)修改。
另外一種可行的方案如圖11所示。ROM位于地址0x1000開始的地方,但是在系統(tǒng)復(fù)位時(shí)又被存儲(chǔ)器控制器映射到0x0000地址處。這樣當(dāng)系統(tǒng)啟動(dòng)之后,在地址0x0000看到的是ROM,系統(tǒng)執(zhí)行這塊ROM中的啟動(dòng)代碼,啟動(dòng)代碼跳轉(zhuǎn)到真正的ROM的地址,并讓存儲(chǔ)器控制器移除對(duì)ROM的地址映射。這時(shí)0x0000地址處的存儲(chǔ)器又恢復(fù)回了RAM。__main中的代碼把向量表copy到0x0000處的RAM中去,使得異常時(shí)能被正確響應(yīng)。
表1為ARM匯編中執(zhí)行ROM/RAM重定向和映射的一個(gè)例子。它以ARM公司的Integrator平臺(tái)為基礎(chǔ)的,該方法適用于類似ROM/RAM重定向方法的所有平臺(tái)。第一條指令完成從ROM的映射地址(0x00000)到真實(shí)地址的跳轉(zhuǎn)。地址標(biāo)號(hào)instruct_2是ROM的真實(shí)地址(0x180004)。然后通過設(shè)置Integrator平臺(tái)上的相應(yīng)控制寄存器,移除ROM的地址映射。代碼在系統(tǒng)一啟動(dòng)就被執(zhí)行。所有關(guān)于地址重定向/映射的操作必須在C庫函數(shù)初始化之前完成。
*本地存儲(chǔ)器配置
許多ARM處理器都有片上存儲(chǔ)器系統(tǒng),如cache和緊密耦合存儲(chǔ)器(TCM)、存儲(chǔ)器管理單元(MMU)或存儲(chǔ)器保護(hù)單元(MPU)。這些設(shè)備都要在系統(tǒng)初始化過程中正確配置,并且有一些特殊的要求需要考慮。
由前文可知,_main中的C庫函數(shù)初始化代碼負(fù)責(zé)程序運(yùn)行時(shí)的存儲(chǔ)器系統(tǒng)設(shè)置。因此,整個(gè)存儲(chǔ)器系統(tǒng)本身必須得在__main之前完成初始化工作,如MMU或MPU必須在reset handler里面完成配置。
緊密耦合存儲(chǔ)器(TCM)的初始化同樣須在_main之前完成(通常在MMU/MPU之前),因?yàn)橐话愠绦蚨夹枰汛a和數(shù)據(jù)分散裝入TCM。需要注意的是當(dāng)TCM被使能后,不再訪問被TCM屏蔽的存儲(chǔ)器。
關(guān)于cache的一致性問題,如果cache在_main之前使能的話,那么當(dāng)_main里面進(jìn)行從裝載區(qū)到執(zhí)行區(qū)的代碼和數(shù)據(jù)拷貝時(shí)(因?yàn)樵诳截愡^程中指令和數(shù)據(jù)在本質(zhì)上都是被當(dāng)作數(shù)據(jù)處理),指令會(huì)出現(xiàn)在數(shù)據(jù)緩沖區(qū)。避免此問題的方法是在C庫函數(shù)初始化完成后再使能cache。
*Scatter loading與存儲(chǔ)器配置
無論是通過ROM/RAM重定向還是MMU配置的方法,如果系統(tǒng)在啟動(dòng)和運(yùn)行時(shí)存儲(chǔ)器分布不一致,scatterloading文件中的定義就要按照系統(tǒng)重定向后的存儲(chǔ)器分布情況進(jìn)行。
以上文ROM/RAM重定向?yàn)槔?BR>LOAD_ROM 0x10000 0x8000
{
EXE_ROM 0x10000 0x8000
{
reset_handler.o (+RO, +FIRST)
...
}
RAM 0x0000 0x4000
{
vectors.o (+RO, +FIRST)
...
}
}
裝載區(qū)LOAD_ROM被放置在0x10000處,代表了重定向之后代碼和數(shù)據(jù)的裝載地址。
堆棧的初始化
程序中可能用到的處理器模式,都需要定義一個(gè)堆棧指針。
在表2中,堆棧位于stack_base標(biāo)識(shí)的地址中。這個(gè)符號(hào)可以是存儲(chǔ)器系統(tǒng)中的一個(gè)直接地址,也可以在另外的匯編文件中定義,由scatter文件來定義分配地址。表2代碼為FIQ和IRQ模式各分配了一個(gè)256字節(jié)的堆棧,用戶可以用同樣的方法為其他模式也分配堆棧。最簡(jiǎn)單的方法就是進(jìn)入相應(yīng)的模式,然后為SP寄存器指定相應(yīng)的值。如果想使用軟件堆棧檢查,還必須指定一個(gè)堆棧長(zhǎng)度限制值。
堆棧指針和堆棧限制的數(shù)值會(huì)作為參數(shù)自動(dòng)傳遞到C庫函數(shù)的初始化代碼__user_initial_stackheap中,在__user_initial_stackheap中不應(yīng)該修改這些值。
硬件初始化 $sub$$main()
一般來說,應(yīng)該把所有的系統(tǒng)初始化代碼與主應(yīng)用程序分離開來,但是有幾個(gè)例外,比如cache和中斷的使能,需要在C庫函數(shù)初始化之后執(zhí)行。
表3代碼顯示了如何使用 $sub和 $supper 。連接器把呼叫main()的函數(shù)替換成呼叫$sub$$main(),完成cache和中斷的使能,并最終跳向main()。
執(zhí)行模式考慮
為主應(yīng)用程序選擇一個(gè)處理器執(zhí)行模式非常重要,這取決于系統(tǒng)的初始化代碼。
許多在啟動(dòng)過程中使用到的功能,如MMU/MPU的配置、中斷的使能等,只能在特權(quán)級(jí)模式下進(jìn)行。如果需要在特權(quán)極模式下運(yùn)行自己的應(yīng)用程序,只要在退出初始化過程之前改變到相應(yīng)的模式就行了,沒有其他任何問題。
如果使用user模式,必須保證所有只能在特權(quán)模式下執(zhí)行的功能完成之后,才能進(jìn)入user模式。因?yàn)閟ystem模式和user模式使用相同的寄存器組,reset handler應(yīng)該從system模式退出,_user_initial_stackheap在system模式下完成應(yīng)用程序堆棧的初始化。這樣在處理器進(jìn)入user模式后,所有的堆??臻g都已經(jīng)被正確設(shè)置好了。
對(duì)存儲(chǔ)器布局的進(jìn)一步考慮
在scatter文件中分配硬件地址
雖然可以在一個(gè)scatter文件中描述代碼和數(shù)據(jù)的分散布局,但是目標(biāo)硬件中的外設(shè)寄存器,堆棧和heap配置仍然直接采用硬件地址在程序源代碼中進(jìn)行設(shè)置。如果把所有存儲(chǔ)器地址相關(guān)的信息都在scatter文件中進(jìn)行定義,避免在源文件中引用絕對(duì)硬件地址,對(duì)程序的工程化管理是有大好處的。
*在scatter文件中定義目標(biāo)外設(shè)地址
通常外設(shè)寄存器的地址在程序文件或頭文件中定義,也可以聲明一個(gè)結(jié)構(gòu)類型指向外設(shè)寄存器,結(jié)構(gòu)的地址定位在scatter文件中完成。
舉例來說,目標(biāo)定時(shí)器上有2個(gè)32位的寄存器,可以用表4來映射這些寄存器。為了把結(jié)構(gòu)放置在指定的存儲(chǔ)器地址上面,創(chuàng)建一個(gè)新的執(zhí)行區(qū)(見表5)。scatter文件便把timer_regs結(jié)構(gòu)定位在了地址0x40000000。
注意,在啟動(dòng)過程當(dāng)中這些寄存器的內(nèi)容不需要清零,改變寄存器的內(nèi)容可能影響系統(tǒng)狀態(tài)。在執(zhí)行區(qū)上加UNINIT屬性可以防止ZI數(shù)據(jù)在初始化過程中被清零。
在scatter文件中分配堆棧和heap
在許多情況下,用scatter文件來定義堆棧和heap的地址會(huì)帶來一些好處,主要有:所有的存儲(chǔ)器分配信息集中在一個(gè)文件里;改變堆棧和heap的地址只要重新連接就行了,不需要重新編譯。
*顯式地放置符號(hào)
在ADS1.2環(huán)境下,這是最簡(jiǎn)單的方法。在前文中引用過2個(gè)符號(hào)stack_base和heap_base,這2個(gè)符號(hào)在匯編模塊中創(chuàng)建,在scatter文件中各自的執(zhí)行區(qū)里定位(見表6)。
表7文件中,heap基地址定位在0x20000上,堆?;刂肺挥?x40000?,F(xiàn)在heap和堆棧的位置就可以非常方便地進(jìn)行編輯了。
*使用連接器產(chǎn)生的符號(hào)
這種方法需要在目標(biāo)文件中指定好heap和堆棧的長(zhǎng)度。這在一定程度上減弱了本節(jié)開頭描述的兩個(gè)優(yōu)點(diǎn)。
首先在匯編源程序中定義heap和堆棧的長(zhǎng)度。關(guān)鍵詞SPACE用來保留一塊存儲(chǔ)器空間,NOINT則可以阻止清零操作(見表8)。注意在這里的源文件中并不需要地址標(biāo)號(hào)。
然后這些部分就可以在scatter文件中對(duì)應(yīng)的執(zhí)行區(qū)里定位了(見表9)。連接器產(chǎn)生的符號(hào)指向每一個(gè)執(zhí)行區(qū)的基地址和長(zhǎng)度限制,這些符號(hào)可以被_user_initial_stackheap調(diào)用的重定向代碼使用。在代碼中使用DCD來給這些值定義更有意義的名字,可以增強(qiáng)代碼的可讀性(見表10)。
文件把heap基地址定位在0x15000,堆棧地址定位在0x4000。Heap和堆棧的位置可以通過編輯對(duì)應(yīng)執(zhí)行區(qū)的地址方便地改變?!?BR>
評(píng)論