裴俊宇 劉子龍
摘要:針對嵌入式操作系統(tǒng)不支持外部程序動(dòng)態(tài)加載技術(shù),限制其靈活性和可擴(kuò)展性的弊端,基于STM32F103μc/OSIII平臺(tái),運(yùn)用ELF文件重定位原理,對原系統(tǒng)功能進(jìn)行拓展,對編譯合適的ELF文件、ELF加載器實(shí)現(xiàn)等作深入分析。測試表明,系統(tǒng)支持對軟件模塊的動(dòng)態(tài)加載功能,并實(shí)現(xiàn)外部任務(wù)之間的通訊。最后針對內(nèi)存開銷大的問題作出改進(jìn),使該項(xiàng)技術(shù)具備更高的實(shí)用價(jià)值。
關(guān)鍵詞關(guān)鍵詞:動(dòng)態(tài)加載;μC/OS;單地址空間;ELF文件
DOIDOI:10.11907/rjdk.171728
中圖分類號(hào):TP319
文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào)文章編號(hào):16727800(2017)011014904
0引言
在電子技術(shù)迅速發(fā)展下,嵌入式系統(tǒng)不僅在硬件性能上快速提升,能勝任更多任務(wù),而且在應(yīng)用上也越發(fā)廣泛,軟件復(fù)雜度越來越高,使得對嵌入式軟件開發(fā)提出了新的要求[1]。相比PC操作系統(tǒng)上早已十分成熟的動(dòng)態(tài)加載技術(shù),常見的μcos嵌入式操作系統(tǒng)缺乏對動(dòng)態(tài)加載程序的支持,一是ROM技術(shù)限制,二是內(nèi)存使用緊張,無法運(yùn)行太多程序[2]。Flash技術(shù)的發(fā)展及內(nèi)存的增加,使得基于嵌入式操作系統(tǒng)的動(dòng)態(tài)加載具備可行性。一些高端嵌入式設(shè)備已經(jīng)具備GB級的內(nèi)存,硬件支持虛擬地址空間,能夠運(yùn)行Linux量級的系統(tǒng)。但實(shí)時(shí)性不滿足要求,性能過剩且成本偏高。作為嵌入式市場主力的中低端設(shè)備,普遍仍是單地址空間,內(nèi)存受限,此時(shí)提高系統(tǒng)的可擴(kuò)展性迫在眉睫。
本文基于STM32平臺(tái),討論在典型單地址空間、無內(nèi)核態(tài)的μcos嵌入式操作系統(tǒng)上動(dòng)態(tài)加載技術(shù)實(shí)現(xiàn),并針對嵌入式內(nèi)存使用緊張問題作出改進(jìn)。
1基本原理與機(jī)制
傳統(tǒng)嵌入式開發(fā)通常將程序編譯鏈接成一個(gè)二進(jìn)制文件,然后燒錄到芯片上運(yùn)行調(diào)試。從簡單的裸機(jī)程序到操作系統(tǒng)與應(yīng)用綁定編譯的復(fù)雜任務(wù),都要通過反復(fù)重新編譯鏈接下載調(diào)試流程,周期長而繁瑣,不能快速迭代,對于市場快速變化需求而言顯得效率低下。此外,每次運(yùn)行程序都是將特定的任務(wù)載入內(nèi)存,不能像PC端一樣安裝、卸除程序,每一次變動(dòng)都要重新燒錄整個(gè)程序,無法靈活地調(diào)整更新[3]。
通常而言,代碼經(jīng)過編譯和鏈接生成二進(jìn)制可執(zhí)行文件,其中鏈接分為靜態(tài)鏈接、加載時(shí)鏈接和運(yùn)行時(shí)鏈接。靜態(tài)鏈接即將程序和它需要的庫鏈接成一個(gè)單一文件,獨(dú)立運(yùn)行、速度快,但是生成文件較大,如果要改動(dòng)就需重新鏈接。加載時(shí)鏈接指程序在連接時(shí)不會(huì)把外部引用的庫代碼鏈接到執(zhí)行程序中,而是在它被加載器加載時(shí)將外部庫加載入內(nèi)存進(jìn)行鏈接。優(yōu)點(diǎn)是程序本身小、靈活。運(yùn)行時(shí)鏈接是加載時(shí)鏈接的更進(jìn)一步,只有在真正調(diào)用外部庫代碼時(shí)才將外部庫加載入內(nèi)存進(jìn)行鏈接[4]。
嵌入式動(dòng)態(tài)加載實(shí)現(xiàn)主要采用加載時(shí)鏈接,即從外部存儲(chǔ)設(shè)備中加載程序到內(nèi)存中,在操作系統(tǒng)中動(dòng)態(tài)申請一個(gè)任務(wù)棧,讓外部加載的程序在該任務(wù)棧上運(yùn)行,并服從操作系統(tǒng)資源管理,同時(shí)程序也能申請一些資源,如內(nèi)存塊、定時(shí)器等。
2系統(tǒng)實(shí)現(xiàn)
單地址空間即整個(gè)系統(tǒng)使用一個(gè)連續(xù)的單地址空間。無用戶態(tài)與內(nèi)核態(tài)之分,用戶程序的代碼可以直接訪問到操作系統(tǒng)代碼。
動(dòng)態(tài)模塊設(shè)計(jì)參考了Linux中動(dòng)態(tài)庫文件的實(shí)現(xiàn)原理,采用Unix標(biāo)準(zhǔn)的ELF格式文件。該文件格式通用性廣、可拓展性強(qiáng),對arm體系編譯器有很好的支持。通過設(shè)置恰當(dāng)?shù)木幾g和鏈接參數(shù),生成可重定位位置無關(guān)代碼[56]。
實(shí)現(xiàn)過程中主要解決如下問題:①解決編譯問題,好的外部程序必須是位置無關(guān)代碼,可以加載到任意內(nèi)存地址運(yùn)行,而燒錄程序要額外生成一個(gè)符號(hào)表,以便動(dòng)態(tài)鏈接;②操作系統(tǒng)通過文件系統(tǒng)讀取文件,能夠加載識(shí)別配置文件和程序;③加載模塊加載外部程序需進(jìn)行鏈接,使得操作系統(tǒng)能夠找到外部程序入口,外部程序能夠調(diào)用系統(tǒng)函數(shù),操作軟硬件資源;④解決外部任務(wù)之間的通訊。
2.1編譯器與鏈接器參數(shù)
采用KeilIDE,默認(rèn)編譯器為armcc,為了方便鏈接,使用編譯器宏命令導(dǎo)出符號(hào)表,以便下一步鏈接。
此處參考armcc手冊,使用宏命令建立一個(gè)結(jié)構(gòu)數(shù)組,存儲(chǔ)符號(hào)和地址信息。
struct my_module_symtab
{
void *addr;
const char *name;
};,
#define SYM_EXPORT(symbol)
const char __sym_##symbol##_name[] SECTION(".rodata.name") = #symbol;
const struct my_module_symtab __sym_##symbol SECTION("SymTab")=
{
(void *)&symbol,
_sym_##symbol##_name
}
存儲(chǔ)符號(hào)名和符號(hào)地址。其中,SECTION()命令是armcc專屬宏指令,編譯時(shí)將該變量放在指定Seciton。__sym_##symbol##_name創(chuàng)建一個(gè)字符串常量存儲(chǔ)符號(hào)名,struct my_module_symtab用來存儲(chǔ)該符號(hào)名和符號(hào)地址。
由于該結(jié)構(gòu)變量都存儲(chǔ)在SECTION("SymTa b")中,因此編譯預(yù)處理后將順序分布在SymTabsection中。通過armcc的宏&MySymTabMYMMYMBase訪問section的首元素,&MySymTabMYMMYMLimit訪問尾元素,像數(shù)組一樣操作符號(hào)表。SYM_EXPORT()用來輸出變量和符號(hào),提供重定位時(shí)的參考信息。
外部程序選用armnoneeabigcc作為交叉工具鏈。編譯器參數(shù)如表1所示。endprint
該命令將編譯出所需的ELF文件格式,其中包含重定位符號(hào)表,對應(yīng)已經(jīng)生成好的符號(hào)表,進(jìn)行重定位[7]。
2.2外部程序加載到內(nèi)存
在片外Flash上建立fatfs文件系統(tǒng)。Fatfs系統(tǒng)通過usb接口傳輸ELF文件。其中配置文件選項(xiàng)如表3所示。
系統(tǒng)啟動(dòng)后加載文件系統(tǒng),等基本驅(qū)動(dòng)初始化完畢后,加載配置文件。配置文件名固定,程序名不固定。根據(jù)配置文件的加載程序數(shù)量,依次對任務(wù)進(jìn)行加載,創(chuàng)建新任務(wù)。需要配置好任務(wù)優(yōu)先級、任務(wù)周期及任務(wù)棧大小,還有可選的傳入?yún)?shù),最后進(jìn)入OSStart()啟動(dòng)任務(wù)循環(huán)。Usb庫和文件系統(tǒng)都是移植現(xiàn)成的,配置文件為方便使用采取JSON格式,JSON解析使用cJSON庫。
2.3ELF加載器實(shí)現(xiàn)
操作系統(tǒng)加載外部ELF文件到內(nèi)存后需解析ELF文件,將其中的有用信息提取出來,主要是代碼段、數(shù)據(jù)段及重定位表。重定位表的作用是指出需要重定位項(xiàng)在ELF文件的偏移位置,根據(jù)重定位段和符號(hào)表進(jìn)行重定位操作,最后將程序的入口交給操作系統(tǒng),再由操作系統(tǒng)創(chuàng)建新任務(wù)啟動(dòng)運(yùn)行,此時(shí)成功加載了一個(gè)外部程序[78]。
2.3.1ELF文件頭解析
解析ELF header,加載到內(nèi)存,編譯出來的文件是Shared Object File,也即Type字段為DYN,ELF Header檢查正常進(jìn)入下一步[9]。根據(jù)ELF Header后緊跟SectionHeader和SectionHeaderStringTable,其中給出了ELF文件中所包含內(nèi)容的具體信息。使用readELF工具打印出ELFheader,其各Section如圖1所示。
圖1ELFSectionList
其中,.rel.dyn中包含了重定位信息,.symtab記錄了符號(hào)表。通過Section Header中offset定位各表,即可在下一步重定位操作[10]。
2.3.2重定位
重定位之前,需要先將代碼和數(shù)據(jù)加載到內(nèi)存。.text中包含代碼、.rodata只讀數(shù)據(jù)段、.data數(shù)據(jù)段,.bss是未初始化全局變量數(shù)據(jù)段,需要預(yù)留出足夠空間。.symtab和.strtab及.symtab,.rel.dyn用于重定位,工作完成后可丟棄。
根據(jù)重定位表中信息,確定重定位類型,從而確定重定位地址計(jì)算方式。重定位項(xiàng)結(jié)構(gòu)體[11]如下:
struct ELF32_rel {
ELF32_Addr r_offset;
ELF32_Word r_info;//SYMBOL<<8+TYPE&0xff.
};
//r_offset是需要進(jìn)行重定位的地址;
//r_info包含了重定位項(xiàng)的基本信息;
//SYMBOL是重定位以后需要指向的符號(hào);
//TYPE是重定位的類型
重定位過程就是重定位表的遍歷,依次將每個(gè)符號(hào)地址進(jìn)行重定位[12]。
重定位類型很多,但大多數(shù)很少出現(xiàn)。通過對編譯器參數(shù)的設(shè)置,使得產(chǎn)生的重定位類型簡單且容易定位。大多為R_ARM_GLOB_DAT、R_RAM_ABS32,及R_ARM_REL32型[1314]。
假設(shè)addr為加載到內(nèi)存后的符號(hào)指向地址,sym_val為上述結(jié)構(gòu)體中的r_offset,則3種類型的定位方式如表4所示。
2.3.3任務(wù)創(chuàng)建
重定位完成后,調(diào)用操作系統(tǒng)創(chuàng)建OS_TCB和CPU_STK并將資源分配給新任務(wù),同時(shí)外部程序?qū)⑷肟诮唤o操作系統(tǒng)以便創(chuàng)建新任務(wù)。操作系統(tǒng)入口在鏈接時(shí)已經(jīng)指定好main函數(shù),ELF header中包含EntryPointAddress即為程序入口。其中,OS_PRIO、CPU_STK_SIZE、OS_TICK、Argument由配置文件指定[15]。
OSTaskCreate((OS_TCB*)&MyAppTCB,/* Create the start task*/
(CPU_CHAR*)"Exteral Program1",
(OS_TASK_PTR ) EntryPointAddress,
(void*) Argument,
(OS_PRIO) MyAppPriority(8),
(CPU_STK*)&MyAppStk[0],
(CPU_STK_SIZE)MY_APP_STK_SIZE(512) / 10,
(CPU_STK_SIZE) MY_APP_STK_SIZE,
(OS_MSG_QTY) 5u,
(OS_TICK) MyTick(1000),
(void*) 0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR*)&err);
2.4任務(wù)通訊
任務(wù)間通訊分為兩種情況:原生程序和外部程序間通訊、外部程序和外部程序間通訊。已知原生程序之間通訊通過全局變量實(shí)現(xiàn),通過信號(hào)量或互斥量防止競爭。加載外部程序時(shí),如果符號(hào)表中有同名的全局變量,會(huì)重定位到原生程序中的同名全局變量;如果沒有,則為全局變量分配獨(dú)立內(nèi)存,并將其名稱和地址加入到符號(hào)表中,由此實(shí)現(xiàn)外部程序和原生程序之間的通訊。
外部和外部程序之間,可以通過約定好的全局變量進(jìn)行通訊,但這樣不夠靈活;也可通過調(diào)用系統(tǒng)函數(shù)實(shí)現(xiàn)通訊,使用操作系統(tǒng)提供消息機(jī)制。從任務(wù)抽象來看,原生任務(wù)和外部加載任務(wù)并無不同,一律通過調(diào)用系統(tǒng)函數(shù)接口、消息隊(duì)列發(fā)送消息,信號(hào)量、事件標(biāo)志組實(shí)現(xiàn)同步。endprint
3內(nèi)存改進(jìn)
在小型嵌入式設(shè)備中,內(nèi)存是一種緊缺資源,因此多數(shù)程序內(nèi)存在使用上都是精打細(xì)算,早在編程階段就已規(guī)劃好。但對于動(dòng)態(tài)加載的外部程序,內(nèi)存浪費(fèi)十分嚴(yán)重。一個(gè)程序運(yùn)行只需要代碼段和數(shù)據(jù)段即可,但實(shí)際過程中把整個(gè)ELF文件都加載進(jìn)了內(nèi)存,ELF文件頭、重定位表等在加載后即失去作用,屬于冗余信息。
嵌入式燒錄好的代碼在ROM上運(yùn)行,但外部程序代碼卻在RAM上運(yùn)行,實(shí)際上是對RAM的浪費(fèi)?,F(xiàn)代硬件的發(fā)展已經(jīng)支持直接對片上Flash的編程,片上Flash可以直接運(yùn)行代碼。為了節(jié)省內(nèi)存,提高內(nèi)存利用率,本文對片上Flash地址空間進(jìn)行管理,在重定位時(shí)直接將代碼段重定位到片上Flash的地址,數(shù)據(jù)保留在RAM中,使得二者像原生代碼一樣分離,達(dá)到了節(jié)省內(nèi)存的目的。甚至對數(shù)據(jù)段也可以規(guī)劃管理,對于相對固定的參數(shù)類數(shù)據(jù)燒入Flash,而變量保留在內(nèi)存里,達(dá)到對內(nèi)存最大效率的利用。已知單地址空間,ROM和RAM只是地址分布不同,因此改進(jìn)地址分布。ROM中的代碼地址重定位指向RAM,地址分布如圖2所示。
圖2地址分布
4測試運(yùn)行
為測試驗(yàn)證軟件模塊是否正常運(yùn)行,基于STM32F103平臺(tái),設(shè)計(jì)不同的外部任務(wù)進(jìn)行加載測試。嵌入式平臺(tái)上最通用的調(diào)試方式是通過LED燈指示程序運(yùn)行狀況。將信號(hào)燈任務(wù)程序編譯為外部程序進(jìn)行加載,該程序?qū)⒄{(diào)用系統(tǒng)函數(shù)和驅(qū)動(dòng)接口,對LED進(jìn)行點(diǎn)亮、閃爍、延時(shí)、流水燈等操作。借助該測試樣例成功驗(yàn)證功能,包括外部程序成功加載、調(diào)用系統(tǒng)函數(shù)及多個(gè)外部程序之間的通訊。
5結(jié)語
本文參考Linux動(dòng)態(tài)加載庫原理,實(shí)現(xiàn)ELF文件動(dòng)態(tài)加載,使得μcosIII支持外部程序的動(dòng)態(tài)加載,提高操作系統(tǒng)的可拓展性和靈活性。將代碼段和數(shù)據(jù)段分離,盡可能使外部加載代碼類似于原生代碼的方式執(zhí)行,對動(dòng)態(tài)加載內(nèi)存開銷大的問題作出改進(jìn),通過配置文件提高靈活性,使該技術(shù)在嵌入式領(lǐng)域具備實(shí)用價(jià)值。不足之處是支持多個(gè)任務(wù)模塊之間的通信,但依賴加載順序,對Static型不支持,在編碼過程中需注意Static變量使用,采用全局變量進(jìn)行替代。本文闡述的動(dòng)態(tài)軟件模塊機(jī)制有待進(jìn)一步完善。
參考文獻(xiàn)參考文獻(xiàn):
[1]張丹.嵌入式系統(tǒng)引導(dǎo)加載程序分析[J].軟件,2012,7(9):129132.
[2]李忠儒.嵌入式系統(tǒng)的發(fā)展趨勢[J].辦公自動(dòng)化,2011,35(11):3537.
[3]王婧怡,應(yīng)忍冬,周玲玲.微內(nèi)核系統(tǒng)直接加載文件機(jī)制的設(shè)計(jì)與研究[J].信息技術(shù),2009,15(10):7376
[4]RANDALLHYDE.深入理解計(jì)算機(jī)[M].韓海東,譯.北京:電子工業(yè)出版社,2006:295300.
[5]陳紫卿,孫昕.FreeRTOS動(dòng)態(tài)軟件模塊[J].計(jì)算機(jī)與現(xiàn)代化,2016,8(6):2428.
[6]鄭映,張祖平.基于ARM+μCOSII的程序動(dòng)態(tài)加載實(shí)現(xiàn)方案[J].艦船電子工程,2009,29(5):8890.
[7]RICHARD M STALLMAN,THE GCC DEVELOPER COMMUNITY.Using the GNU compiler collection (GNU tools for ARM embedded processors)[M].GNU Press,2016.
[8]楊偉,羅蕾.嵌入式系統(tǒng)中的模塊動(dòng)態(tài)加載技術(shù)[J].單片機(jī)與嵌入式系統(tǒng)應(yīng)用,2015,23(11):810
[9]李培亮,李振鵬.嵌入式單地址空間操作系統(tǒng)動(dòng)態(tài)加載的研究[J].電子測試,2010,8(7):2327.
[10]朱裕祿.系統(tǒng)下的文件分析[J].電腦知識(shí)與技術(shù)學(xué)術(shù)交流,2006,17(26):6466
[11]何先波,唐寧九,呂方,等.文件格式及應(yīng)用[J].計(jì)算機(jī)應(yīng)用研究,2001,18(11):144145
[12]袁鴻野.基于嵌入式操作系統(tǒng)的動(dòng)態(tài)鏈接器設(shè)計(jì)與實(shí)現(xiàn)[D].成都:電子科技大學(xué),2013.
[13]寧濤.面向嵌入式應(yīng)用的動(dòng)態(tài)加載機(jī)制研究.[D]重慶:重慶大學(xué),2008.
[14]TOOL INTERFACE STANDARD(TIS).Executable and linking format (ELF) specification.version1.2[S].2010.
[15]JEAN J.Labrosse μC/OSIII reference manual[M].US:Micriμm Press,2015.
責(zé)任編輯(責(zé)任編輯:孫娟)endprint