高艷鹍,劉 華,劉朝暉
(1.北京計算機技術及應用研究所,北京 100039;2.南華大學,湖南 衡陽 421001)
傳統的微處理器不適用于進行數字信號處理所需要的高等數學運算,其運算需要較長的計算時間,不能滿足現代信號實時處理的要求。而DSP所具有的系統構成靈活、可編程、使用面廣的特點,使其在通信、航空航天、醫療儀器、工業控制及信息家電中成為不可或缺的數字信息處理的計算引擎。TMS320C6678(簡稱C6678)是美國德州儀器(Texas Instrument)于2011年10月推出的一款高性能浮點嵌入式數字信號處理器(DSP)[1]。作為一款8核的DSP,C6678可以滿足在軍事、工業等領域的實時數據處理的性能要求。C6678提供了多種芯片外設接口,支持多樣化總線協議,包括RapidIO、PCIe、I2C、EMIF、UART、SPI總線及千兆網,GPIO、TSIP等[2]。
天熠嵌入式操作系統彈載版是航天二院706所針對彈上計算機開發的嵌入式操作系統產品,目前針對TI的C6000系列DSP能夠支持C6713、C6672、C6678,提供多種基礎的內核服務。目前,使用TI公司的TMS系列通用DSP處理器做系統開發需要使用專門的開發工具CCS,通過JTAG接口的仿真器將程序下載到目標機調試運行。在某些比較復雜的應用背景下,需要將用戶應用程序通過靜態鏈接生成一個獨立的可執行鏡像下載執行,用戶每次修改一行程序都需要將整個程序重新編譯下載。當可執行程序較大時,下載過程就會消耗用戶較長的時間,效率不高。
在商業的VxWorks操作系統上已經針對X86、PowerPC等處理器實現了應用程序軟件模塊的動態加載機制,先將內核運行起來,然后通過內核加載應用程序。當應用程序修改后,也只需要先卸載已經在系統中的該軟件模塊,再重新加載模塊,大大提高了開發的效率。動態加載機制的核心技術是延遲鏈接,涉及到處理器的體系結構、編譯器技術、可執行程序的ABI(Application Binary Interface)三方面內容,目前業界尚未有商用的操作系統能夠針對TI的DSP處理器平臺提供動態加載機制。為在彈載領域滿足用戶的使用,在基于TI C6678 DSP處理器的天熠嵌入式操作系統中增加動態加載機制做了技術探索,并研發了動態加載器作為天熠嵌入式操作系統產品組件。
隨著設備集成度越來越高,功能越來越復雜,在一個設備上運行的軟件往往需要不同的廠商提供軟件模塊一起配合運行,各模塊提供的服務往往又需要被其他模塊調用,例如模塊A作為主體應用模塊需要調用模塊B提供的濾波計算服務,調用模塊C提供的設備服務,模塊B在執行濾波計算時又需要操作系統提供的內存服務接口的存儲器分配功能。
傳統的開發方式需要在軟件開發時將不同的軟件模塊靜態鏈接成一個單獨的可執行程序并下載到目標機上[3]。該方式的一個缺陷是需要不同的廠商提供對應模塊的源碼,基于知識產權的保護,廠商一般會將源碼封裝為靜態庫的形式提供給主程序的研制方,但需要幾方廠商必須同時開發完成并提交成果,否則將無法編譯鏈接[4]。在開發時不同廠商軟件產品的難度,需要的資源往往無法協調一致,進度無法保證,作為需要使用其他模塊的主應用則無法構建出一個完整的可執行應用,及早地開展自己這部分的測試工作。軟件模塊獨立運行視圖和傳統靜態鏈接的程序運行視圖如圖1。

圖1 兩種運行方式視圖對比Fig.1 Comparison of two operation modes
通常在調試階段,程序開發人員對DSP進行編程。首先,是在PC機上使用DSP廠商提供的調試開發軟件平臺編譯程序,而后通過DSP板的JTAG調試接口將程序下載到DSP中運行。在實際應用中,通常需要將DSP程序固化在DSP板上FLASH或者EEPROM中,系統上電后,程序自動從FLASH中加載至DSP內部存儲區并且執行。但上述這兩種方法都需要使用額外的JTAG線來連接主機和DSP板,對于已經裝配完畢的密封設備,如果需要更改程序,必須將設備進行拆裝,重新安裝JTAG線進行調試[5]。從工程應用的角度來看,頻繁地對已經裝配完畢的設備進行拆裝,會嚴重影響整個系統的穩定性和可靠性。為了避免這種情況的發生,應選擇在不拆裝設備的同時遠程對DSP板上程序進行動態加載[6]。
現階段,基于現有的同外部設備連接的總線接口進行程序下載的主要技術實現通過在目標機上固化一小段引導程序bootloader,啟動后引導程序負責目標機的運行環境的初始化,并通過外部設備接口(一般為串口)同宿主機進行交互,下載應用程序并固化到Flash上,并通過跳轉語句將處理器執行的控制權交給應用程序[7]。
該種方式確實可以滿足應用程序升級的需求,但有兩點不足:第一點是引導程序和應用程序對內存的使用布局需要在開發時通過鏈接腳本協同規劃,從而兩個程序相互依賴;第二點是被加載的對象只能是獨立的可執行應用程序,bootloader無法加載多個應用[8]。
在一些高可靠的控制計算機系統設計階段會提出可重構設計需求,完成功能定制、動態升級、故障恢復。目前,筆者接觸到的對于長期在軌運行的星載計算機領域對該需求尤為迫切。例如,某星載設備的相機控制系統是針對地面物體進行拍攝的高端相機的核心控制部件,相機對調焦的控制算法需要根據地物高度、衛星在軌位置、速度、姿態等相關因素計算調焦距離[9]。由于是首次研制,缺少歷史的相關試驗數據,對最終的相機成像效果通過地面試驗及仿真不能充分有效驗證,無法確定在衛星發射后載荷相機對地面物體的成像能否達到預定的目標。基于此,研制方設計了相機控制系統的在軌升級機制,相機的控制系統使用的C5000的DSP處理器,上電啟動后先運行一段引導程序,由引導程序判斷是否從遙測控制接收到對控制程序的在軌更新命令。當接收到命令時,更新核心的控制程序[10]。但設計中存在一個不足:需要控制計算機的重新上電,運行引導程序。
通過以上幾點分析,在由TI C6000 DSP構成的復雜、高可靠嵌入式系統中引入基于動態鏈接機制的動態加載解決方案有多方面的工程需求。在以天熠嵌入式操作系統彈載版的基礎上,開展了研究和設計。
完成動態加載及鏈接牽涉不同方面的計算機技術,其中一個必要條件是編譯器必須能夠支持生成延遲重定位的目標程序。這需要編譯器在編譯時能夠暫時屏蔽掉未定址的符號而不會報編譯錯誤,同時為生成地址無關的代碼要插入特定的樁代碼形式或過程鏈接表。該目標程序同可執行程序的區別是可執行程序是完全具備執行能力的程序,不需要任何其他模塊輔助支持。而動態庫程序本身不具備執行能力,其中存在引用的符號處于未定址的狀態,如果強制執行,則處理器會進入到異常狀態。
根據TI的編譯器手冊說明能夠生成支持動態鏈接庫的ELF文件格式的編譯器版本應至少為7.2以上。目前國內針對C6000處理器使用的開發環境主要為CCS3.3和CCS5.5。其中,CCS3.3中使用的Cl6x編譯器版本為6.08,CCS5.5的Cl6x編譯器版本為7.4,基于CCS3.3開發環境無法實現動態鏈接庫生成。
動態鏈接庫的加載過程緊密依賴于TI定義的EABI(ELF Application Binary Interface)文件格式。該格式是TI公司根據標準ELF文件格式自定義的一種文件格式,依據其標準定義主要分為3類,具體見表1。

表1 ELF文件的主要類型Table 1 Main types of ELF files
目前,可使用的文件格式為共享目標文件。從連接和運行的角度,可以分別把目標文件的組成部分劃分為以下兩種視圖,具體如圖2。

圖2 連接視圖和運行視圖對比Fig.2 Comparison of connection view and operation view
表2為ELF文件格式中各段的作用,在實際加載過程都是以“段”作為處理對象。

表2 ELF文件格式的各段作用和說明Table 2 Functions and descriptions of each paragraph of ELF file format
目標模塊被加載到內存空間后,不能立即運行。動態加載機制還須對目標模塊進行處理,解決模塊的外部引用(符號解析)和重定位,這一步是動態加載過程中的最關鍵環節。由于模塊是被單獨編譯成共享目標文件,因而在生成目標文件時,調用的其他模塊或函數庫中的函數和全局變量(統稱為符號)的地址仍處于不確定狀態。以對全局函數的引用舉例說明編譯器插入的stub形式及重定址過程。源碼如下,import關鍵字標識其為導入的符號,其作用是指示編譯器該符號需要加載時綁定地址。

該源碼編譯成add.dll后查看其反匯編的代碼:

在目標模塊中,對.plt段中sym符號的地址以0x0000的形式寫入到MVK指令的地址碼,并通過B10寄存器直接尋址跳轉到sym函數,而.text段中add函數對sym符號的引用通過對.plt段的相對尋址訪問(兩個端加載后的相對偏移量保持不變)。在重定址時只需根據sym符號的實際地址和記錄的引用位置修改MVK指令的地址碼。
3.1.1 動態加載器的分層設計
動態加載管理采用分層式設計,由用戶接口層、管理層、執行層3個層次組成,如圖3。

圖3 動態加載器的分層結構Fig.3 Hierarchical structure of dynamic loader
1)用戶接口層:負責同用戶進行交互的界面,提供用戶關于動態加載相關命令的輸入、解析、執行,具體見表3。

表3 用戶接口的主要命令集合Table 3 Main command sets of user interface
2)管理層:管理層主要利用內核提供的多種服務加載模塊,對加載模塊的ELF文件格式有效性進行判斷,保存和維護模塊的相關信息,并形成加載模塊的關系鏈表。
3)執行層:主要負責對加載模塊外部引用的全局符號進行重定位,完成動態鏈接過程。
3.1.2 與天熠操作系統內核其他服務的關系
考慮到天熠嵌入式操作系統自身的微內核架構,動態加載器在設計上規劃為一個獨立的操作系統組件,可以跟隨系統進行功能裁剪,并充分利用天熠操作系統已有的系統服務完成動態加載器的設計,如圖4。

圖4 內核組件服務的調用過程Fig.4 Calling process of kernel component service
Shell組件先使用網絡組件提供的ftp服務、內存管理組件、文件系統組件將軟件模塊下載到文件系統,然后再通過動態加載組件完成模塊的動態加載及動態鏈接。
3.2.1 內存布局規劃
嵌入式系統資源使用一般規劃的比較嚴格,尤其在內存使用方面。DSP程序的開發需要通過cmd文件的鏈接腳本預先規劃好內存的整體布局。主機系統對動態庫的加載主要是由進程控制塊來維護整個32位虛擬地址空間的內存使用,通過查詢空閑空間找到未使用區域后,再加載動態庫。因為動態庫加載后隨著應用生命周期一直運行,不會像堆數據空間的內容、大小經常性變化。基于以上特點,采取在鏈接腳本中規劃出固定大小的內存空間BLOB區域專用于動態庫加載。加載器會計算加載的模塊總共占用內存空間的大小,當預先規劃的加載空間不足時會通過Shell向用戶提示,需要用戶重新規劃內存空間布局。
鏈接腳本如下:

3.2.2 加載的模塊管理
加載器的功能主要是完成動態庫文件的加載,包括ELF文件頭解析,文件格式有效性判斷。對實際加載的模塊信息進行維護,包括文件頭、程序頭、加載段等信息,為后續的模塊卸載、依賴性分析提供支撐。描述模塊描述信息的結構體定義如圖5,并通過DLIMP_Loaded_Module*loaded_module指針建立已加載模塊的維護鏈表快速遍歷模塊的相關信息。

圖5 加載模塊的管理Fig.5 Load module management
3.2.3 加載的段管理
依據ELF文件格式定義,其包含了諸多段,但并不是所有段對模塊的實際運行有作用。根據內存訪問模型和編譯器選項,在動態加載過程中需要加載同實際運行相關.plt、.got、.data、.text段,詳見表4。

表4 加載段的作用Table 4 Functions of loading section
最終加載到實際運行內存空間的段視圖,如圖6。

圖6 模塊加載后的.BLOB塊內存視圖Fig.6 BLOB Block memory view after module loading
動態鏈接器將dll模塊加載完成后,根據導入符號的名稱遍歷查找已加載的Base映像的導出符號,將符號地址填入到.plt、.got段中,使已加載的模塊具備執行能力,如圖7。

圖7 重定址過程Fig.7 Re addressing process
本文通過分析在嵌入式系統中軟件模塊動態加載的使用需求和動態鏈接原理,并在天熠嵌入式操作系統的產品中實現了基于C6678處理器的軟件模塊動態加載組件。該組件可以完成多個用戶定義模塊的自動(文件系統)、手動(網絡或串口終端)加載及卸載,可用于支持嵌入式系統的功能重構、產品的動態升級、故障恢復,最終使整個系統擴展性和靈活性大大提高,較好地滿足了用戶的實際需要。