蔡富強,郭兵,沈艷
(1.四川大學 計算機學院,成都610065;2.電子科技大學)
蔡富強(碩士生),主要研究方向為嵌入式實時系統(tǒng);郭兵(教授),主要研究方向為嵌入式實時系統(tǒng)、SoC和中間件;沈艷(副教授),主要研究方向為虛擬儀器、分布式測試。
嵌入式系統(tǒng)是一個專用的計算機系統(tǒng),它在現實生活中有著極其廣泛的用途,大到飛機、宇宙飛船,小到各種手持設備(如手機),都是嵌入式系統(tǒng)的具體應用。可以說,嵌入式系統(tǒng)已經漸漸融入我們的生活,變得不可或缺。這種趨勢將隨著物聯網[1]的推廣變得更加明顯。
嵌入式系統(tǒng)的一個比較關鍵的技術就是嵌入式操作系統(tǒng)。目前常用的嵌入式操作系統(tǒng)有 Linux、μC/OS[2]、WinCE、VxWorks、Symbian等,其中μC/OS以其開放源碼、高效而小巧,同時又兼具實時性等特點得到了比較廣泛的應用。
μC/OS中只實現了操作系統(tǒng)的一些基本功能(帶優(yōu)先級可搶占的進程管理和一個比較簡單的內存管理方案),這是它只有幾千行代碼的基本原因。正因為如此,它并不適用于所有嵌入式應用,尤其是面向高端的應用。為了拓展μC/OS的應用范圍,很多改進措施被提出。一些學者為μC/OS添加功能模塊,以拓展其應用,如TCP/ⅠP協議棧、GUⅠ、文件系統(tǒng)等,參考文獻[3]為μC/OS添加內存文件系統(tǒng),參考文獻[4]為μC/OS添加基于FAT的Flash文件系統(tǒng);也有學者改進μC/OS的基本模塊,以滿足新的需求,參考文獻[5]改進了μC/OS的任務調度模塊,參考文獻[6]解決μC/OS消息隊列數據通信安全問題;參考文獻[7]擴展μC/OS到可重構系統(tǒng),使其支持軟硬件任務的統(tǒng)一調度。
本文也以拓展μC/OS應用為目的,為μC/OS添加加載外部可執(zhí)行程序支持,同時也提出一種內存分配方案,以實現為程序映像和棧高效分配內存。由于目前在嵌入式領域應用最多的處理器為ARM系列,本功能的實現針對ARM平臺。
μC/OS內核實現的內存分配方案支持多種大小的內存塊,但不同大小內存塊之間獨立管理,這種實現使用不方便,不能實現動態(tài)而高效的內存分配。本文提出一種獨立的內存管理機制,以實現為程序映像和棧高效分配內存。
實現參考了Linux內核的分區(qū)頁面管理算法[8]。將全局內存劃分為以512字節(jié)為單位的塊,并為每個內存塊建立描述符,通過描述符來管理內存塊。內存塊按塊數的幾何級數組織成多級鏈表,第一級用于分配1個塊,第二級用于分配地址連續(xù)的2個塊,第三級用于分配地址連續(xù)的4個塊,依次類推。由連續(xù)內存塊的首塊內存的描述符參與鏈表鏈接。將實現下面幾個函數,具體描述見1.2小節(jié)。
實現該內存管理,需要一些全局變量和數據結構定義。ⅠNT8U*pmem指向靜態(tài)分配的全局內存。塊描述符的定義如下:
BLOCK_DESC表示內存塊描述符,每個內存塊都有一個內存描述符,并且從前到后一一對應。next和prev用于多級鏈表的鏈接;order的最高位表示該連續(xù)的內存塊序列是否被使用,其余位表示所屬鏈表級數。鏈表級數根據全局內存大小自動調整,在嵌入式應用中一般不超過10(能分配最大512KB的連續(xù)內存),用ⅠNT8Ugorder表示鏈表的級數。BLOCK_DESC*desc_begin表示塊描述符的首地址。void*block_begin表示內存塊的首地址。BLOCK_DESC*desc_list[11]用于鏈接多級鏈表。
DescToBlock函數實現從內存塊描述符地址到內存塊地址的轉換。由于塊描述符與內存塊依次對應,只要有兩者的內存首地址,就很容易實現。具體實現只需要下面的表達式:return(char*)block_begin+ (desc-desc_begin)<<9。BlockToDesc函數功能正好相反,實現方式類似。
BlockMemⅠnit函數完成該內存管理方案的初始化。首先,根據提供的全局內存的大小確定多級鏈表級數,最多為10級,并初始化全局變量gorder,內存塊數為size/(512+sizeof(BLOCK_DESC));然后,根據全局內存能夠容納的內存塊數將其分為兩部分,前面用于存儲所有的塊描述符(將塊描述符清零),后面部分就是用于分配的內存塊,并初始化全局變量block_begin和desc_begin;最后,將內存塊按最大化的原則分配于多級鏈表中,理想情況下所有的內存塊都分配于所支持的最大級鏈表中,大小不滿足的則分配到低級別的鏈表中(仍須按最大化原則)。
圖1表示初始化后的內存布局(虛線表示不一定存在)。
GetBlocks函數用于分配(1<<order)個連續(xù)的內存塊。算法描述如下:
① 檢查參數order是否有效,無效返回null,有效繼續(xù);
②檢查order級鏈表是否有空閑內存,沒有轉下一步,否則從鏈表中取出第一塊,修改描述符的使用標志位,通過函數DescToBlock獲取內存地址并返回;
③ 從更高級別獲取空閑內存,如果獲取失敗,返回null,否則,逐級分裂直到order級鏈表,并修改首個描述符的所屬鏈表級數,轉第二步。
圖1 初始化后的內存布局
FreeBlocks函數用于釋放指定地址處開始的(1<<order)個連續(xù)的內存塊。該函數是實現高效內存分配的關鍵,為了保證內存不會被分得過碎,需要迭代檢查釋放內存是否可以同相鄰的內存合并(看描述符order字段是否相同)。算法描述如下:
①通過函數BlockToDesc獲取塊描述符的地址,將描述符設置為未使用;
② 檢查order級鏈表中是否有與待釋放內存相鄰的內存(前后都有相鄰的選前面的參與合并)。如果沒有相鄰的,轉第③步。否則,將相鄰內存的首個內存塊描述符從鏈表中刪除,修改相關的兩個塊描述符的order字段(在前面的加1,后面的清零),參數order加1,繼續(xù)執(zhí)行第②步;
③ 將內存插入order級鏈表中。
要實現外部程序加載,需要操作系統(tǒng)和編譯器的密切配合,整個過程非常復雜。為了使程序加載器盡可能簡單和高效,采用下面的措施:
① 修改編譯μC/OS內核的Makefile文件,使內核鏈接地址和加載地址相同,通過NM工具導出內核函數地址表。將地址表以函數指針的形式組織在頭文件中,另外還須在頭文件中加入內核中關于數據類型和結構的定義,供外部程序使用。以本文第一部分中的BlockMemⅠnit函數為例,假設函數地址位于0x00008000,在頭文件中作如下聲明即可:
② 使main函數的代碼位于程序映像的入口處(為簡單起見,main函數不支持參數)。只需采用下面措施即可。編程時保證main函數是所在文件中第一個被實現的函數,修改LD鏈接腳本使包含main函數的目標文件的代碼段位于整個可執(zhí)行程序的開始。
③通過OBJCOPY工具將鏈接生成的ELF格式的可執(zhí)行程序轉化為二進制映像。
④ 為μC/OS添加函數void Exec(char*name),用于加載二進制映像,并為其創(chuàng)建進程。
通過以上措施,加載器的實現非常簡單,只需為二進制映像分配內存并將其加載進內存,然后調用函數OSTaskCreate(二進制映像加載地址(main),null,通過函數 GetBlocks分配的棧空間,優(yōu)先級)即可,由于ARM平臺沒有直接內存尋址模式,二進制映像不需要重定位就可執(zhí)行。圖2為Exec函數的流程。
圖2 Exec函數執(zhí)行流程
測試時采用S3C2440系列開發(fā)板作為目標平臺。首先,修改u-boot,以使其能夠加載μC/OS到指定地址處,并完成內核的引導工作;其次,移植μC/OS,使其能夠在S3C2440系列開發(fā)板上運行;然后,按參考文獻[3]或[4]中提供的方法為μC/OS實現文件系統(tǒng)支持;最后,按本文中的方法實現二進制映像加載功能。經測試,該方案是可行的。當然,該方案也存在一定的局限性,如只針對ARM平臺,內存分配方案還不能通用(對小內存塊分配利用率低),又由于μC/OS內核不支持保護模式,安全性也是一個問題。
[1]王保云.物聯網技術研究綜述[J].電子測量與儀器學報,2009,23(12):1-7.
[2]Labrosse Jean J.嵌入式實時操作系統(tǒng)μC/OS-ⅠⅠ[M].邵貝貝,等譯.2版.北京航空航天大學出版社,2003.
[3]張紅兵.大容量內存文件系統(tǒng)設計及μC/OS下的實現[J].單片機與嵌入式系統(tǒng)應用,2004(3):15-20.
[4]王命延.一種加載在μC/OS-ⅠⅠ內核上的嵌入式文件系統(tǒng)[J].南昌大學學報:理科版,2005,29(2):197-204.
[5]張旭.μC/OS ⅠⅠ內核任務調度模塊的分析與改進[J].單片機與嵌入式系統(tǒng)應用,2005(4):71-76.
[6]曾蜀芳.μC/OS-ⅠⅠ中消息隊列通信的數據安全問題[J].計算機技術與發(fā)展,2009,19(8):151-154.
[7]周博.SHUM-UCOS:基于統(tǒng)一多任務模型可重構系統(tǒng)的實時操作系統(tǒng)[J].計算機學報,2006,29(2):208-218.
[8]Bovet Daniel P,Cesati Marco.深入理解Linux內核[M].陳莉君,等譯.2版.北京:中國電力出版社,2004:223-268.