動態模塊加載和ELF Loader

很早很早以前就想在嵌入式系統上實現動態模塊加載的功能了,期間走了些彎路,直到最近,才完整地在嵌入式系統上實現動態模塊加載。

=== 動態模塊加載的好處 ===

動態模塊加載的好處很多,例如,當你升級一個系統的時候,可以只升級一個模塊,而不必升級整個系統。你可以把不同的模塊放在不同的介質上,並實施不同等級的保護,例如BIOS部分進行寫保護。

有些系統允許用戶進行二次開發,這個時候幾乎一定是需要動態加載功能的,因爲你不希望用戶需要鏈接整個系統才能夠進行二次開發,而且你可能希望支持多個用戶模塊,彼此不相互依賴,彼此不干擾。

=== Background ===

一般來說,C的編譯器編譯出來的代碼,由以下幾個重要的部分:
.code: 代碼段
.data: 有初值的數據段
.bss: 無初值的數據段

通常還有.rodata,是隻讀的數據段,在嵌入式系統中經常可以合併到.code段中.

注: .code, .data 和.bss這些段的命名不同的編譯器可能會有不同。

由於不同段在實際運行的時候可能會被加載到不同的介質,例如.code和.rodata可以放在NOR FLASH上而.data,.bss放入RAM中,或者要滿足所謂的scatter loading,因此編譯器會努力使段可以自由移動。

但是要做到這一點,並不容易。

在代碼段中運行的指令,要獲取數據段中的數據,方法有:
a) 通過當前PC值+偏移量
b) 通過絕對地址
c) 通過中間寄存器,寄存器裏面:
c.1) 存放絕對地址
c.2) 偏移量

方法b通常只在CISC中存在,許多RISC機器由於指令長度受限制,並不存在方法b。

因此,從這裏可以看出,要做到各段可以自由移動,有幾種方法:
1) 保留一個寄存器專門用於指示數據段的起始地址
2) 運行前修改指令
3) 保留一小塊數據段和代碼段的相對位置不變,此片數據段作爲指向實際數據段的入口表, 運行前修改此表。

方法1和方法3通常會結合起來一起用,動態鏈接庫就是用了這種技術。
方發2是一種通用的方法,實際上連接器就是這樣生成可執行文件的。

=== ARM AIF ===

ARM公司的編譯器有一項特殊的功能,即可以產生一種可自我重定位的可執行文件,即AIF格式。
在AIF文件中,包含了一個AIF頭和一小段由編譯器產生的重定位代碼。運行AIF格式的文件,只需要告訴它起始地址,這段重定位代碼就會負責修改餘下的一些必要的信息達到重定位目的。目前還沒有充分的公開的文檔解釋AIF內部的詳細工作機制。

在我過去的一些項目中,AIF工作的很好,但是運行時外部無法獲取AIF文件的更多信息,例如你無法去調用AIF映像中的某一個函數,因爲你不知道它的地址。另外,AIF的執行映像中,.data必須緊跟在.code之後,對於想重定位到FLASH中執行的嵌入式系統就行不通了。

=== ELF ===

ELF文件是最常見的目標文件格式,它可能有很多擴展名,例如.o,.so,或者最終的可執行文件也是ELF。

ELF有幾種:
* 可重定位
* 可動態鏈接
* 可執行
* 可執行+可重定位

可執行的ELF如果沒有可重定位信息,那就只能靠虛擬內存系統來支持它運行。但是對於許多嵌入式系統,有可能連MMU都不具備,因此我們只關心可重定位的ELF。(可動態鏈接ELF實際上也是可重定位的一種,附加很多額外信息)

有關ELF的詳細信息,請參閱:http://www.skyfree.org/linux/references/ELF_Format.pdf

=== ELF Loader ===

我花了不少時間尋找小型的ELF Loader實現,但是真正適合嵌入式系統的卻不多。

+Contiki OS:
在Contiki OS裏面,有一個很有趣的ELF Loader實現,嗯,其實Contiki OS有很多有意思的東西 :)

+ucLinux:
ucLinux也是一個很有意思的例子。由於ucLinux沒有啓用虛擬內存系統,因此它在加載可執行文件的時候,就要進行重定位。爲了加速重定位和減小ELF文件的體積,ucLinux提供了特殊的工具鏈,在產生ELF之前進行部分的“預重定位”,最後ELF中只需要攜帶很小體積的重定位信息。

+其它RTOS:
其它嵌入式OS,如VxWorks也實現了ELF Loader, eCos的ELF Loader看起來尚未完整。

+Linux Kernel:
哦,差點忘了一個最重要的,Linux Kernel。

Linux Kernel的模塊是可以通過insmod動態地加入內核。雖然Linux的用戶空間程序運行在虛擬內存中,整個內核的空間確只有一個。一些奉行micro kernel的人批評Linux的這種方式,但是一個單一空間的內核運行效率卻是最高的。

在2.6內核中,模塊重定位工作不再由insmod來完成,而是由內核來做所有的重定位工作。實現代碼在:kernel/module.c中。

剝去那些處理特殊section的代碼,Linux內核模塊加載部分的代碼其實是非常簡單明瞭的,而且Linux支持數十種架構意味着你幾乎不要擔心架構移植的問題。

=== 結論 ===

在嵌入式系統中實現動態模塊加載的技術是成熟的,可靠的,可以借鑑的開發源碼的實現例子也有不少。一個參考數據: 我最近在一個嵌入式RTOS上實現的ELF Loader,運行在ARM7 CPU上,從NAND FLASH中加載一個400K左右的ELF,耗時大約0.5秒。


=== THE END ===
發佈了4 篇原創文章 · 獲贊 1 · 訪問量 2463
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章