引言
在當今的軟件設計中,為了在不同的產品線上重復使用相同的代碼,需要將代碼從一個平臺移植到另一個平臺。雖然這種代碼的重復利用非常重要,但是很少有人討論該采用何種方法來減少設計可移植軟件的成本,本文將介紹一種可行的設計方案。
可移植代碼的重要性
過去,嵌入式應用程序一般不需要運行在不同的硬件平臺和不同的操作系統上,因此開發者也不需要考慮讓代碼運行到多平臺上的問題。
隨著技術的發展,市場競爭的激烈,廠家必須不斷開發新產品。如果是需要為新產品重新設計新軟件,一來開發周期長,另一方面,代碼要經過嚴格測試后才能推出,就會影響產品的面市時間。因此,將已有的、已經過測試的代碼移植到新產品上就是一個很好的辦法,進而開發者被要求 設計出可運行在不同硬件平臺或不同操作系統下的應用程序。但是,由于系統設計可選擇不同的設備、不同的子系統以及CPU,范圍非常廣泛,這樣的不確定性勢必會增加開發多平臺軟件的難度。
使用抽象層設計可移植代碼
抽象層的先進之處在于它能夠提取應用程序的基本內容,并將其與系統實現相隔離。這里關鍵是要將應用程序的邏輯算法與其運行環境相隔離。例如,在讀取文件時,應用程序應該只關心文件的內容,而不在意文件是存在于FAT文件系統中,還是EXT2文件系統中;在數據通信中,通信雙方應該只關心收發的數據本身,至于數據傳輸的媒體則不是應用程序該考慮的。
多平臺軟件的系統構成如圖1所示,這種方法可廣泛地應用在各種應用程序中。就像系統的機械組件可以被抽象,通信接口、CPU資源、存儲設備等也可以被抽象。靈活使用抽象層可以將應用程序簡化為一個緊湊并可重復利用的代碼。
圖1 多平臺軟件系統構成圖
操作系統抽象層
操作系統函數永遠不要被直接調用,應將其包裝到一個“操作系統抽象層”的庫中,把應用程序從底層的操作系統中脫離出來。于是,當將應用程序移植到其他操作系統時,只要簡單地移植操作系統抽象層即可,無需修改應用程序的代碼。 這不僅將原應用程序的正確性和可靠性帶到了新平臺上,還加速了移植過程。此外,當在向新平臺移植的過程中發現錯誤時,操作系統抽象層將是調試的最有效的起點。
操作系統抽象層應該輸出函數原型,同時,不管底層的操作系統是如何實現的,抽象層應該對應用程序隱藏它們的一些特殊行為。例如,拿對信號量的操作來說,在大多數操作系統中,相同的進程可以多次獲取一個信號量,而某些操作系統在遞歸獲取信號量時會阻塞調用者。這里假設操作系統抽象層庫輸出一個信號量獲取函數OS_SemTake(),那么它必須禁止該系統的標準操作,完成通用信號量獲取的功能,使該函數獨立于底層的操作系統。在遞歸獲取信號量這個例子中,開發者必須實現遞歸行為本身。具體實現中,一些操作系統可能要比較兩個任務的ID,這兩個任務一個是上次要求獲取信號量的任務,另一個是本次要求獲取信號量的任務。如果兩個ID相同,說明是在同一進程中,函數將增加信號量的計數器,且不調用操作系統的信號量獲取函數。相應的,信號量釋放函數必須對信號量的計數器進行遞減操作,直到計數器為0,才調用操作系統 程序中編寫的函數封裝應該保證在所有的操作系統中具有相同的行為,這可以避免底層操作系統的特殊性對應用程序的影響。
Socket庫函數中的select()提供了一個很好的例子來說明函數封裝的重要性。對于不同的操作系統來說,可選擇的設備有相當大的差異,一些系統只允許選擇插口,而其它系統可以選擇插口、管道和消息隊列。在移植過程中,對底層操作系統實現的抽象使應用程序免于不必要的、雜亂的修改。如果某個操作系統沒有實現select()的超時功能,只要在抽象層庫中就能夠完成修改。否則,就需要對應用程序的結構進行重大修改。
邏輯接口
這一步需要將物理接口轉換為邏輯接口。比如一個系統中所有的外圍設備都連接到一個總線上,那么就可以建立一組訪問總線的函數,例如讀總線數據函數BusReadFrom()、向總線發送數據函數BusSendTo()等,這可以有效地將總線操作從應用程序中提取出來。這樣,無論總線是用PCI、VME、串口或其它形式實現,都可使用邏輯接口將應用程序與物理接口隔離開。
邏輯接口特別適用于數據通信應用。如果一個系統利用TCP/IP協議在以太網上參與一次會話,那么可將會話從IP連接中劃分出來,通過編寫函數來實現邏輯會話,如:SessionOpen()、SessionClose()、SessionSend()、SessionReceive()等。邏輯接口將底層的物理接口隱藏,使得應用程序便于移植到不同的物理接口上。真正的移植工作將在邏輯接口中完成,而會話實現沒有改變,改變的是會話傳輸的媒體。如果能很好地創建邏輯接口,應用程序將能移植到不同類型的、具有多種接口的設備中。
當應用程序需要運行在多種處理器上時,可以使用相同的方法實現應用程序代碼。將與處理器相關的代碼提取出來,應用程序就可以自由地從一種處理器移植到另一種處理器上。例如進程間的通信機制,只要在抽象層中完成不同處理器的進程間通信功能,應用程序就可以毫無修改地運行在新的處理器上。
控制系統也可以使用相同的方法,控制系統只關心命令本身,至于命令到底是來源于人機命令接口、還是紅外線、或是SNMP代理,都不是控制系統要關注的。控制命令的輸入機制應該被分離出來,形成獨立的一層。
協議隔離
實現某種協議時,協議本身不應與具體的傳輸媒體打交道,同時應用程序中的一些特殊功能也不該在協議中實現。
協議的最終傳輸應在抽象層中完成,其核心部分只完成數據處理工作。這種代碼的組織方式可以將協議自由地嵌入到各種設備中。同時,還可以不依賴硬件而測試協議自身的正確性。因此,在硬件完成之前就可以調試代碼,當硬件完成時即可保證協議是正確的,如果有錯誤則應該發生在與硬件的接口上或硬件本身。
還有一點很重要,一定不要將應用程序的一些特殊功能放到協議中,這樣會影響協議的獨立性。應用程序的特殊功能也應該放到抽象層中,而不是在協議中實現。
使用系統服務
系統服務是一組軟件組件,它向應用級軟件提供服務。系統服務隱藏各種硬件平臺的差異,向用戶提供一組標準服務。系統服務能用來管理非易失性存儲器、處理器資源、提供硬件級的精確定時服務。同時,它還能用于管理繼電器、開關或其他外圍硬件。
系統服務還可以提供不依賴于某些特殊的物理硬件的軟件服務,包括進程間通信、軟件健壯性檢查、事件日志、時間戳服務等。
建立程序框架
為了使程序便于移植,還有一個工作就是建立應用程序框架,將所有與硬件平臺相關的初始化代碼放到一個模塊當中。當系統啟動時,首先調用與平臺相關的框架模塊來對系統硬件進行初始化和置。一旦這些特殊的硬件的信號量釋放函數釋放信號量。初始化完畢,框架便開始執行應用程序。由于這些特殊的平臺代碼是與應用程序分離的,因此應用程序可以運行在任何其它的框架結構中。
當開發的應用程序需要運行在不同平臺上時,最好要考慮兩種開發環境的差異。比如:Windows和Linux。有些Windows環境在文件路徑中既可以使用斜杠“/”,也可以使用反斜杠“\”,而Linux環境中只能使用斜杠“/”。Linux對文件的大小寫敏感,因此程序中頭文件的大小寫必須與文件系統中的完全匹配,而在Windows系統中則沒有這種要求。另外,這兩種環境下的編輯器對特殊字符的處理也不盡相同,這些字符包括:“Tab”、換行、“Enter”等。在開發過程中需要避免因為這些差異而要修改已完全測試通過的源代碼的情況。
實例
接下來本文將以一個TCP/IP協議棧的開發例子來介紹抽象化的設計方法。首先為它規劃一個框架:
Core:TCP、IP協議的核心代碼;
API:與上層代碼實現接口的函數;
Arch:與體系相關的代碼;
Core部分是實現TCP/IP協議的核心代碼,不是本文要討論的內容。
API部分是協議棧提供的與高層應用的接口,主要實現TCP/IP協議的標準接口,包括select、綁定函數bind、監聽函數listen、接收連接函數accept等,此外還有各種IP地址轉換函數、各種TCP/IP協議用到的數據結構等。
Arch部分是協議棧與體系結構相關的代碼,主要實現與操作系統以及與處理器相關的代碼。與處理器相關的內容包括:數據結構的對齊方式、存儲器的存儲格式(大端或小端)、協議棧用到的數據類型等。與操作系統相關的內容包括:進程處理、信號量處理、消息隊列處理、定時功能等。
Netif部分是協議棧使用的網絡接口代碼,主要實現IP包在物理媒體上的傳輸,實際上是數據鏈路層和物理層的實現。
設計時將TCP/IP協議本身放到Core部分,而將其它的放到抽象層中,這樣,協議運行的操作系統支持由Arch部分完成,傳輸媒體支持由Netif部分完成,與高層的應用由API部分完成。當需要將協議移植到新的平臺上時,要做的工作是修改Arch部分,利用新的操作系統提供的函數實現進程處理、信號量處理、消息隊列處理和定時功能等,并根據所用處理器修改與處理器相關的內容。若網絡接口發生改變,例如使用另一種芯片時,則只要完成Nefif中的相應驅動即可。
結語
抽象是多平臺開發強有力的工具。通過抽象,把應用程序的核心部分分離出來,將其它系統支持部分在抽象層中完成,這樣就能夠把代碼的移植工作集中在抽象層里。合理地利用抽象,就能以最小的代價完成代碼的移植。
zrbj:gt
-
cpu
+關注
關注
68文章
10855瀏覽量
211606 -
應用程序
+關注
關注
37文章
3267瀏覽量
57683
發布評論請先 登錄
相關推薦
評論