?
13.2? 編譯器的缺省行為
多數(shù)嵌入式應(yīng)用程序最初都是在原型環(huán)境下開(kāi)發(fā)的。無(wú)論什么樣的原型仿真環(huán)境與最終產(chǎn)品環(huán)境都是有差異的。因此,考慮如何將嵌入式應(yīng)用程序從其所依賴的開(kāi)發(fā)工具或調(diào)試環(huán)境中移植到在目標(biāo)硬件上獨(dú)立運(yùn)行是非常重要的。
開(kāi)始編寫嵌入式應(yīng)用程序時(shí),開(kāi)發(fā)者可能并不清楚目標(biāo)硬件的具體規(guī)格。如,目標(biāo)系統(tǒng)使用了什么樣的外圍設(shè)備、存儲(chǔ)器映射情況甚至不能確定處理器的型號(hào)。
為在了解這些詳細(xì)信息前能夠繼續(xù)軟件的開(kāi)發(fā),RVCT工具提供了很多默認(rèn)的操作,使用戶能編譯和調(diào)試與目標(biāo)系統(tǒng)無(wú)關(guān)的應(yīng)用程序代碼。下面詳細(xì)介紹這些編譯選項(xiàng),只有深入了解這些編譯選項(xiàng)設(shè)置,才能使開(kāi)發(fā)更順利的進(jìn)行。
13.2.1? Semihosting
1.Semihosting簡(jiǎn)介
在RVCT C庫(kù)中,對(duì)某些ISO C功能的支持由主機(jī)調(diào)試環(huán)境提供。提供該功能的機(jī)制被稱為Semihostin[1]。大多數(shù)的ARM調(diào)試系統(tǒng)都支持Semihosting機(jī)制,如ReslView Debugger AXD等。
調(diào)試系統(tǒng)提供這種機(jī)制是非常有用的,因?yàn)橛糜陂_(kāi)發(fā)使用的硬件系統(tǒng)經(jīng)常沒(méi)有最終系統(tǒng)的所有輸入和輸出設(shè)備。在這種情況下,Semihosting可讓主機(jī)代替目標(biāo)系統(tǒng)提供這些設(shè)備的功能。舉例來(lái)說(shuō),此機(jī)制可以用于啟用C庫(kù)中的函數(shù)(例如,printf()和scanf())使用主機(jī)的屏幕和鍵盤,而不使用目標(biāo)系統(tǒng)的屏幕和鍵盤。
半主機(jī)由一組已定義的SWI操作來(lái)實(shí)現(xiàn)。應(yīng)用程序調(diào)用相應(yīng)的SWI,然后由調(diào)試代理程序(Debug Agent)處理SWI異常。調(diào)試代理程序完成系統(tǒng)與主機(jī)之間的通信。
圖13.1顯示了Semihosting機(jī)制的處理過(guò)程。
圖13.1? Semihosting機(jī)制的處理過(guò)程
在很多情況下,Semihosting SWI由庫(kù)函數(shù)內(nèi)的代碼調(diào)用。應(yīng)用程序也可以直接調(diào)用。支持ARM C庫(kù)中Semihosting的詳細(xì)信息,請(qǐng)參閱ARM相關(guān)文檔。
2.Semihosting軟件接口
ARM和Thumb SWI指令包含一個(gè)軟中斷號(hào),該中斷號(hào)可以被應(yīng)用程序使用。此編號(hào)可以由系統(tǒng)中的SWI處理程序進(jìn)行解碼。有關(guān)SWI處理程序的詳細(xì)信息,請(qǐng)參閱本書中ARM異常處理一節(jié)。
Semihosting使用固定的中斷號(hào)調(diào)用相應(yīng)的處理程序。用于Semihosting的SWI是:
·? 0x123456(在ARM狀態(tài)下);
·? 0xAB(在Thumb狀態(tài)下)。
注意 |
用戶在編寫自己的中斷處理程序時(shí),避免使用Semihosting已經(jīng)使用的中斷向量號(hào)。 |
調(diào)試代理通過(guò)SWI的中斷向量號(hào)識(shí)別該軟中斷是目標(biāo)系統(tǒng)提出的Semihosting請(qǐng)求。具體是何種Semihosting請(qǐng)求(鍵盤輸入請(qǐng)求或屏幕顯示請(qǐng)求),通過(guò)向寄存器r0傳遞不同的參數(shù)進(jìn)行區(qū)分。所有其他參數(shù)通過(guò)一個(gè)數(shù)據(jù)塊進(jìn)行傳遞。該數(shù)據(jù)塊的地址通過(guò)寄存器r1傳遞給中斷處理程序。軟中斷的處理結(jié)果放在r0中返回,也可以通過(guò)顯式的返回值或傳遞數(shù)據(jù)塊的指針帶回程序的處理結(jié)果。即使未返回結(jié)果,也假定r0是被使用的。
用r0傳遞的可用Semihosting操作編號(hào)分配如下:
·? 0x00-0x31? 這些編號(hào)由ARM公司使用;
·? 0x32-0xFF? 這些編號(hào)由ARM公司保留,以備將來(lái)使用;
·? 0x100-0x1FF? 這些編號(hào)保留給用戶應(yīng)用程序。
注意 |
雖然這些編號(hào)ARM公司不使用,用戶可以使用這些編號(hào)編寫自己的SWI操作,但建議使用其他 SWI 編號(hào),而不要使用Semihosting SWI 編號(hào)和這些Semihosting的預(yù)留操作類型編號(hào)。 |
·? 0x200-0xFFFFFFFF這些編號(hào)未定義。當(dāng)前未使用并且不推薦使用這些編號(hào)。
在以下部分中,操作名稱之后的括號(hào)中的編號(hào)是調(diào)用Semihosting操作時(shí)放入r0的值。例如,SYS_OPEN(0x01)。
如果從匯編語(yǔ)言代碼中調(diào)用SWI,最好使用semihost.h中定義的操作名稱??梢杂?EQU 偽操作定義操作名稱。例如:
SYS_OPEN??? EQU 0x01
SYS_CLOSE?? EQU 0x02
?
3.Semihosting需求函數(shù)
Semihosting需要的函數(shù)列表如表13.1所示。如果使用默認(rèn)的Semihosting功能,用戶不需要編寫任何其他代碼。也可以重新實(shí)現(xiàn)部分的輸入/輸出函數(shù),使這些函數(shù)和標(biāo)準(zhǔn)Semihosting混合使用。
表13.1????? Semihosting函數(shù)列表
函 數(shù) 名 稱 |
描??? 述 |
SYS_OPEN (0x01) |
打開(kāi)文件 |
SYS_CLOSE(0x02) |
關(guān)閉使用SYS_OPEN打開(kāi)的文件 |
SYS_WRITEC (0x03) |
向控制臺(tái)輸出字符 |
SYS_WRITE0 (0x04) |
將空終止的字符串寫入控制臺(tái) |
SYS_WRITE (0x05) |
寫入主機(jī)上的文件 |
續(xù)表
函 數(shù) 名 稱 |
描??? 述 |
SYS_READ (0x06) |
將文件內(nèi)容讀取到緩存器 |
SYS_READC (0x07) |
從控制臺(tái)讀取字節(jié) |
SYS_ISERROR (0x08) |
確定返回代碼是否錯(cuò)誤 |
SYS_ISTTY (0x09) |
檢查文件是否連接到交互設(shè)備 |
SYS_SEEK (0x0A) |
搜索到文件中的某個(gè)位置 |
SYS_FLEN (0x0C) |
返回文件的長(zhǎng)度 |
SYS_TMPNAM (0x0D) |
返回文件的臨時(shí)名稱 |
SYS_REMOVE (0x0E) |
刪除主機(jī)上的文件 |
SYS_RENAME (0x0F) |
重命名主機(jī)上的文件 |
SYS_CLOCK (0x10) |
執(zhí)行開(kāi)始后的厘秒數(shù) |
SYS_TIME (0x11) |
1970 年 1 月 1 日到現(xiàn)在的秒數(shù) |
SYS_SYSTEM (0x12) |
將命令傳遞給主機(jī)命令行解釋程序 |
SYS_ERRNO (0x13) |
獲得 C 庫(kù) errno 變量的值 |
SYS_GET_CMDLINE (0x15) |
獲得用于調(diào)用可執(zhí)行程序的命令行 |
SYS_HEAPINFO (0x16) |
獲得系統(tǒng)堆參數(shù) |
SYS_ELAPSED (0x30) |
獲得自執(zhí)行開(kāi)始的目標(biāo)滴答聲數(shù)目 |
SYS_TICKFREQ (0x31) |
確定滴答聲的頻率 |
?
13.2.2? C 庫(kù)結(jié)構(gòu)
從概念上來(lái)講,C庫(kù)函數(shù)可被化分成兩類,一類為ISO C語(yǔ)言的規(guī)范部分,該部分的主要功能是向用戶提供一個(gè)調(diào)用接口;另一類為ISO C語(yǔ)言規(guī)范提供支持。圖13.2顯示了這兩類函數(shù)在C庫(kù)中的結(jié)構(gòu)。
圖13.2? C庫(kù)的函數(shù)結(jié)構(gòu)
對(duì)部分ISO C功能的支持是由主機(jī)調(diào)試環(huán)境在支持函數(shù)的設(shè)備驅(qū)動(dòng)程序級(jí)別提供的。
例如,RVCT C庫(kù)通過(guò)寫入調(diào)試器控制臺(tái)窗口來(lái)實(shí)現(xiàn)ISO C printf()系列函數(shù)。通過(guò)調(diào)用__sys_write()來(lái)提供該功能。這是一個(gè)執(zhí)行半主機(jī)SWI的支持函數(shù),使字符串被寫入到控制臺(tái)。
13.2.3? 默認(rèn)存儲(chǔ)器映射
對(duì)于沒(méi)有描述存儲(chǔ)器映射的映像(Image),RVCT根據(jù)默認(rèn)存儲(chǔ)器映射放置代碼和數(shù)據(jù)。默認(rèn)的存儲(chǔ)器映射如圖13.3所示。
圖13.3? 默認(rèn)存儲(chǔ)器映射
結(jié)合圖13.3,可以看出默認(rèn)的存儲(chǔ)器映射使用以下規(guī)則:
·? 鏈接映像,在地址0x8000加載并運(yùn)行。首先放置所有的RO(只讀)段,其次是RW(讀寫)段,然后是ZI(零初始化)段。
·? 堆(Heap)直接從ZI段的頂端地址算起,因此,其準(zhǔn)確位置在鏈接時(shí)決定。
·? 棧(Stack)的起始地址在應(yīng)用程序啟動(dòng)過(guò)程時(shí)由Semihosting操作提供。具體Semihosting操作設(shè)置的值由調(diào)試系統(tǒng)的不同而不同。
① RealView ARMulator ISS(RVISS)設(shè)置為配置文件peripherals.ami中設(shè)定的值。默認(rèn)值是0x08000000。
② Multi-ICE將該地址設(shè)置為調(diào)試器內(nèi)部變量top_of_memory的值。默認(rèn)值是0x00080000。
?
13.2.4? 鏈接程序放置規(guī)則
鏈接程序遵守一組規(guī)則,以決定代碼和數(shù)據(jù)位于存儲(chǔ)器中的什么位置,如圖13.4所示。
鏈接程序放置遵循以下規(guī)則:
① 映像首先按屬性組織:RO段在最低的存儲(chǔ)器地址,其次是RW段,然后是ZI段。每一種屬性中,代碼在數(shù)據(jù)之前。
② 鏈接程序按名稱的字母順序放置輸入段(Section)。輸入段名稱即匯編程序AREA偽操作定義的名稱。
圖13.4? 鏈接程序放置規(guī)則
③ 在輸入段中,獨(dú)立對(duì)象的代碼和數(shù)據(jù),按照對(duì)象文件在鏈接程序命令行中被指定的順序放置。
要精確放置代碼和數(shù)據(jù),ARM公司建議不要過(guò)分依靠這些規(guī)則。相反,必須使用分散加載機(jī)制來(lái)完全控制代碼和數(shù)據(jù)的放置。請(qǐng)參閱下一章的調(diào)整映像存儲(chǔ)器映射以適應(yīng)目標(biāo)系統(tǒng)硬件存儲(chǔ)器的實(shí)際要求。
13.2.5? 應(yīng)用程序啟動(dòng)
多數(shù)嵌入式系統(tǒng)中,執(zhí)行主任務(wù)前,執(zhí)行初始化序列來(lái)設(shè)置系統(tǒng)。默認(rèn)的RVCT初始化序列如圖13.5所示。
圖13.5? 默認(rèn)RVCT初始化序列
在進(jìn)入用戶代碼(main())前,初始化序列可分成三個(gè)功能塊:__main直接跳轉(zhuǎn)到__scatterload;__scatterload負(fù)責(zé)建立運(yùn)行時(shí)的映像存儲(chǔ)器映射,而__rt_entry(運(yùn)行時(shí)的入口)則負(fù)責(zé)初始化C庫(kù)。
__scatterload執(zhí)行代碼和數(shù)據(jù)復(fù)制以及ZI數(shù)據(jù)的清零。對(duì)于ZI數(shù)據(jù)的清零和未改變的RW數(shù)據(jù)來(lái)說(shuō),這一步總是要做的。
__scatterload跳轉(zhuǎn)到__rt_entry。它設(shè)置應(yīng)用程序的棧和堆,初始化庫(kù)函數(shù)及其靜態(tài)數(shù)據(jù),并調(diào)用任何全局聲明的對(duì)象的構(gòu)造函數(shù)(僅C++)。
然后__rt_entry跳轉(zhuǎn)到應(yīng)用程序入口main()。主應(yīng)用程序結(jié)束執(zhí)行時(shí),__rt_entry將庫(kù)關(guān)閉,然后把控制權(quán)交還給調(diào)試器。
RVCT中,函數(shù)main()有一個(gè)特殊含意。main()函數(shù)的存在強(qiáng)制鏈接程序鏈接到__main和__rt_entry中的初始化代碼。沒(méi)有main()函數(shù),就不會(huì)鏈接到初始化進(jìn)程,那么一些標(biāo)準(zhǔn)C庫(kù)功能就不會(huì)得到支持。