說明:本系列文章翻譯自Contiki之父Adam Dunkels經典論文,版權歸原做者所有。java
Contiki是由Adam Dunkels及其團隊開發的系統,研讀其論文是對深刻理解Contiki系統的最佳資料。算法
-----------------------------------------------------------------------------------------------------------------------------------------------------------無線傳感器網絡由大量微小的聯網可通訊設備組成。對於大規模網絡而言,動態地下載代碼到網絡中是極其重要的。在本論文中。咱們描寫敘述了一個支持動態地載入、替換我的儲蓄和服務的輕量級操做系統——Contiki。緩存
Contiki基於事件驅動(event-driven)內核,並提供可選的搶佔式多線程。在一個資源受限的環境中,Contiki能作到保持基礎系統輕量、緊湊的同一時候,還可以靈活地動態載入和卸載程序和服務。安全
無線傳感器網絡由大量具備無線通訊能力的微傳感器設備組成。這些傳感器設備可以自治地構成網絡並數據傳輸。一般狀況下。這些傳感器設備是資源受限的。板載電池或者太陽能面板僅僅能提供有限的電源供應。此外。微小的尺寸和低廉的價格也限制着系統的複雜性。網絡
典型的傳感器的配置包含8位微控制器、大約100kb的代碼存儲空間和小雨20kb的RAM。依據摩爾定律,這些設備在未來會愈來愈小、愈來愈便宜。雖然這意味着傳感器網絡可以被擴展到更大的範圍,但卻不必定說明相關資源會受到更小的限制。多線程
做爲一個爲傳感器節點設計操做系統的設計者連說。其面臨的挑戰在於找到一個輕量級的機制和抽象,以在資源受限的設備上提供足夠豐富的運行環境。咱們已經開發了Contiki——針對資源受限環境而開發的操做系統。Contiki提供了我的程序和服務動態載入、卸載的功能。其內核基於時間驅動(event-driven),並支持搶佔式多線程機制。哪些明白需要多線程的程序將被連接爲一個庫。從而實現可搶佔式多線程。咱們眼下將Contiki執行在ESB平臺[5]。ESB使用的微處理器是MSP430,帶有2kbRAM,60kbROM。執行在1MHz。這個微處理器可以可選地重燒部分片上flash存儲。
架構
本篇論文的貢獻主要在兩個方面。第一是靈活性。一個資源受限傳感器設備之上可載入程序和服務。第二個通用性。搶佔式多線程沒必要再內核最底層實現,而是構建於事件驅動內核頂層做爲應用程序庫。這贊成基於線程的程序執行在基於事件的內核之上,下降系統各部分重入帶來的開支、棧空間。併發
普通狀況下,單獨用人去收集和又一次燒寫所有的傳感器設備是不可行的。其實。眼下已有很是多種方法可以將代碼分發到無線傳感器網絡中。對於這些方法。由於通訊將佔用大部分有效節點能源。因此下降發送到網絡中的字節數是關鍵。框架
大多數用於嵌入式的操做系統都需要編譯、下載一個包括完整系統的二進制鏡像到設備中。
這個二進制鏡像包括操做系統、系統庫和執行在系統之上的實際應用程序。
而與此不一樣的是,Contiki贊成在執行期間載入、卸載我的應用程序和服務。
大多數狀況下,應用程序遠遠小於整個系統,所以在經過網絡傳輸時Contiki需要更少的能量。
隨着不一樣的傳感器設備平臺的添加。咱們需要一個可以在不用硬件平臺移植的通用軟件基礎架構。
當前有效的傳感器平臺上都載有全然不一樣的配置。
由於應用程序相關的特性,咱們指望着部分在未來不會改變。
在今天,平臺間的統一不變的特性僅僅有一個——不使用段機制或者內存保護機制的CPU架構。
在這樣的架構下。程序代碼被存放於可又一次編程的ROM,程序數據被存放於RAM。咱們設計了Contiki操做系統,其提供CPU多線程抽象(且是惟一的抽象)。並支持程序和服務的可載入性。考慮到傳感器網絡中應用的相關特性,咱們以爲其它抽象被設計爲庫或者服務、提供動態機制更合適
因爲每一個線程都有本身的棧,並且因爲一般很是難提早知道一個線程需要多大的棧空間。因此係統給線程預分配棧是老是足夠多的。當線程被建立時。需要爲每一個棧分配內存,棧中的內存僅僅能被相應線程使用。不能被當前執行的多個線程共享。此外,線程併發模型需要鎖機制來保護可以修修改態資源的線程。
爲了在不爲每個線程分配棧、提供鎖機制的同一時候依舊可以提供併發服務。咱們使用了事件驅動機制。
在事件驅動系統中,進程以事件操做的方式實現。由於一個事件操做不能被堵塞。所有的進程可以使用一樣的棧,從而可以在進程間有效地實現稀缺的內存資源的共享。與此同一時候。由於兩個事件操做從不併發地執行,普通狀況下鎖機制也不需要了。
雖然事件驅動系統在很是多種傳感器網絡應用中都能很是好的工做,但是依舊存在問題——程序猿很是難去管理狀態驅動編程模型,換句話說。不是所有的程序均可以很是方便地使用狀態機來描寫敘述,比方password學中的評估算法時間長度。一般,在標準的CPU平臺下,這需要幾秒鐘的時間。
在一個純粹的事件驅動操做系統中,時間長度計算全然獨佔CPU。使系統不能夠響應外部時間。假設操做系統基於可搶佔式的多線程模型,這將不是問題,因爲時間長度計算能被搶佔。
爲告終合時間驅動和可搶佔式線程,Contiki使用了一個混合模型:系統基於時間驅動內核。而將可搶佔式多線程以可選應用庫的形式實現,在程序需要它時才被連接。
本論文剩下的部分以例如如下的形式組織:第2章回想相關的工做。第3章描寫敘述Contiki系統的框架。第4章描寫敘述Contiki內核的設計,第5章描寫敘述Contiki服務的概念,第六、7章描寫敘述Contiki怎樣處理庫、怎樣處理通訊支持,第8章描寫敘述可搶佔式多線程的實現,第9章描寫敘述咱們使用該系統的經驗。最後,第10章做總結。
爲了給TinyOS提供執行時重編程功能,Levis和Culler爲TinyOS設備開發了一個虛擬機——Mate[17]。
這個虛擬機爲了典型傳感器網絡應用而單獨設計的。很是類似地,MagneOS操做系統[7]使用了java虛擬機來將應用分發到傳感器網絡。使用虛擬機而不適用本地機器代碼的優勢是虛擬機代碼可以寫得很是小,在傳輸代碼到網絡中時可以減少能量消耗。
但是其缺點是添加了解釋代碼的能量消耗,因爲對於長期執行的程序而言,在傳輸二進制代碼時節省的能量將在執行代碼時被消耗。
Contiki程序使用本地代碼,所以可以被適用於各類類型的程序,包含不丟失執行效率的底層設備驅動。
SensorWare[8]提供了一個針對可編程傳感器的抽象腳本語言,但是他們的目標平臺不是像咱們這樣的資源受限的平臺。類似的。EmStar[3]環境被設計用於資源更少的受限系統。Reijers和Langendoen[21]使用一個補丁語言去小改處於執行的系統的二進制鏡像。這在所有節點執行一樣二進制代碼的網絡中是有效的,但是一旦有一點點不一樣的程序或者程序的版本號不一樣一時候就變得特別複雜。
Mantio操做系統[3]使用傳統的可搶佔式多線程模型操做。
經過下載程序鏡像到EEPROM,再從EEPROM燒寫到flash ROM, Mantio既支持又一次編程完整的操做系統,也支持又一次燒寫部分存儲空間。由於多線程的理論,每個Mantis程序必須從系統堆分配棧空間,必須使用鎖機制以實現共享變量相互排斥。
相反地。Contiki由於使用基於事件的、沒有搶佔的調度器,依次避免了多棧分配和鎖機制。可搶佔式多線程以庫的方式提供,當需要的時候才被連接到程序。
Contiki中的可搶佔式多線程與fibers[4]相似。
輕量級的fibers有Welsh和Mainland[23]實現。餘輕量級的fibers不一樣的是,Contiki不會限制當前線程數量的最大值——2。此外。不像fibers那樣。Contiki中的線程支持可搶佔式。
與Exokernel[11]和Nemesis[16]相似,Contiki儘可能下降抽象的數量,以保持內核尺寸的最小化。抽象以庫的形式實現,並且差點兒實用下層硬件的全然訪問權限。
Exokernel追求性能、Nemesis追求服務質量,Contiki追求減少尺寸、維持靈活。
與Exokernel不一樣的是,Contiki不支持保護機制,因爲Contiki的硬件在設計時就不支持內存保護。
所有的進程。包含應用程序和服務。可以在執行時動態載入。進程間通訊老是經過內核進行的,但是內核不提供郵件抽象層,而讓設備驅動、應用直接與硬件通訊。
一個進程(process)有一個事件處理函數(event hander)或者可選的輪詢處理函數(poll hander function)定義。進程的狀態保存在進程的私有內存中,內核中僅僅有一個指向該狀態的指針。在ESB平臺[5],進程狀態由23字節組成。所有的進程共享一樣的地址空間。而不是執行在不一樣的保護域。進程間經過郵差事件(posting events)通訊。
核心被編譯成單一的二進制鏡像文件,並被優先部署到設備上。在部署以後。即便使用程序載入器去覆蓋核心或者給核心打補丁,核心通常不會被改動。
程序經過程序載入器載入到系統中。
程序載入器經過使用通訊棧或者直接使用像EEPROM之類的附加存儲設備來得到程序二級制文件。典型地,在程序被燒寫內存以前是存放在EEPROM裏,而後被載入系統。
所有的程序都經過內核低筒的事件派遣或者經過輪詢機制執行。內核一旦被調度後,將不會搶佔時間處理區。所以時間處理器必須執行到結束。只是,將在第8章中看到。時間處理器必須使用內部機制來實現可搶佔式。
內核支持兩種事件:異步和同步時間。異步事件是一種延遲處理調用的一種形式:異步事件是內核裏的一個隊列。在一段時間後會被派遣到目標進程。同步事件與異步事件相似。但是會被立刻派遣到需要被調度的目標進程中去。
當目標進程處理完事件後,會返回結果給郵差進程。這與Spring操做系統中的門抽象相似,可以被看作進程間的程序調用。
除事件以外。內核提供輪詢機制。輪詢可以看作在異步事件中調度的高優先級的事件。進程的輪詢通常用於與硬件相關的操做,比方檢車硬件設備的狀態。
當一個輪詢被調度時,所有的實現了輪詢處理器的進程將會依照他們優先級被調度。
Contiki內核爲所有進程運行提供惟一的共享的棧。由於事件處理器的調用時棧是倒回的(rewound)。異步事件的使用下降了棧空間。
爲了能夠支持一個根本的實時運行體,Contiki從不關閉中斷。因爲這個緣由。Contiki不一樣意中斷處理器派遣事件,因爲那會致使在事件處理器中產生競爭條件。相反地。內核提供了一個輪詢標記。用以請求輪詢事件。這個標記給中斷處理器提供了一個請求立刻輪詢的方法。
將程序被載入到內存後,載入器會調用程序的初始化函數。程序的初始化函數可能會開始或者替換一個或多個進程。
在傳感器網絡中,老是但願當網絡不活動時能夠關閉節點電源以達到減少能量消耗的目的。電源保護機制既依賴於應用[18],也依賴於網絡協議[20]。
Contiki內核包括了非顯式節電抽象。但是需要應用去指定去實現這個機制。爲了能夠幫助應用決定何時關閉系統電源,時間調度器公開了時間隊列的大小。當沒有事件被調度的時候,能夠依據這個信息來關閉處理器。當處理器響應中斷被喚醒的時候,經過輪詢處理器處理外部事件。
圖2.應用函數調用服務的過程
在Contiki中。服務是爲其它進程提供特定功能的進程,可以當作是共享庫的一種形式。服務在執行時可以被動態地替換,所以也必須被動態地連接。
典型的服務包含通訊協議棧、傳感器設備驅動程序、高級別功能(比方傳感器數據處理算法)。
服務層牢牢靠近內核,負責管理服務。詳細地說,服務層負責跟蹤正在執行的服務。並提供一種方法以找到被安裝的服務。一個服務由一個文本字符串所標識,用於描寫敘述該服務。服務層使用通用的字符串匹配方法來查詢已經安裝的服務。
服務由服務接口和實現該接口的進程組成。服務接口由一個版本和一個函數表組成。
函數表由指向該函數接口的函數指針組成。
應用程序經過根庫(stub library)與服務通訊。根庫被連接到應用程序,而後使用服務層找到服務多相應的進程。一旦一個服務被定位,根將服務進程的進程ID緩存起來,用於未來所有的服務請求。
程序經過服務接口根調用服務。在調用服務的過程當中。程序沒必要知道他所調用的接口是用於服務的特殊接口。服務第一次被調用時,服務接口根會在服務層查找服務。
假設指定的服務存在。查找時將返回一個指向該服務接口的指針。然後存在服務接口中的版本將與根中的版本進行檢查。除了版本以外,服務接口中還包括了指向服務函數的指針。假設服務根中的版本與服務接口中的版本成功匹配。服務接口根將調用被請求的函數。
與所有的進程同樣,服務也可以在正在執行的Contiki系統中被動態地載入和替換。
由於服務進程的進程ID用於標識進程,假設服務進程被替換,保留其進程ID是相當重要的。基於這個緣由。內核提供了特殊的機制用於替換進程和保留進程ID。
當一個服務被替換的時候。內核經過給服務進程郵寄一個特殊的事件。做爲對該事件的響應,服務必須從系統中移除本身。
不少服務內部有一個狀態,用於傳遞到新進程。內核提供了一種方法。將一個指針傳遞到新的服務進程,新服務進程可以依據傳入的狀態產生一個狀態描寫敘述符,用於描寫敘述進程的狀態。
保存有狀態的內存必須從共享源裏分配,因爲當老進程被移除的時候會又一次分配進程內存。
服務的狀態描寫敘述符中包括服務的版本。所以內核不會嘗試去載入具備不兼容版本的一樣服務。
Contiki內核僅僅提供最主要的CPU複用和事件處理特性,系統其餘部分以系統庫的形式實現。並被可選擇地連接到程序中。
程序與庫的連接有三種方式。第一種。做爲核心的一部分的庫。被靜態地連接到程序中。另一種,做爲可載入程序的一部分的庫,被靜態地連接到程序中。
第三種,程序可以調用服務來實現一個特定的庫。以服務形式存在的庫,可以在執行時被動態地替換。
典型地,執行時的庫,比方經常被使用的庫。最好放到Contiki核心。很是少被使用,或者應用相關的庫,更適合與可載入程序連接。做爲核心一部分的庫。老是存在系統中,所以沒必要被包括到可載入程序庫中。
舉個樣例。若是程序使用函數memcpy()拷貝內存。使用atoi()將字符串轉變爲整數。函數memcpy()是經常被使用的C庫函數,而atoi()被用得不是那麼多。
所以,在這個特殊的樣例中,memcpy()將被包括到系統核心,而atoi()不會被包括。當程序被連接成二進制時,函數memcpy()的靜態地址將會被再次連接到核心。然而,C庫中實現auoi()的那部分目標代碼必須被包括到程序二進制中。
圖3.通訊棧
通訊是傳感器網絡中的基礎概念。在Contiki中。爲了使執行時替換成爲可能,通訊是以服務的形式實現的。
多個通訊棧被同一時候載入。
經過實驗研究發現。這可以被用於評估和比較不一樣的通訊協議。而且。正如圖3所看到的。一個通訊棧可能會被分紅不一樣的服務,這就是的執行時替換部分通訊棧成爲可能。
通訊服務使用服務機制去調用其它服務,並使用同步事件去與應用程序通訊。
由於同步事件處理器被要求一旦執行起來就必須執行到結束,這就使得所欲的通訊處理使用一個buffer成爲可能。
設備驅動程序讀取接收到的包到通訊buffer中。而後就調用上層的通訊服務。通訊棧負責處理包頭。並郵寄一個同步事件給應用程序,告知已經接收到此包。應用程序對包的內容做出響應。而後可選地在buffer中保持一個響應。最後將控制權返回通訊棧。
通訊棧預先準備需要發送到外部的新的包頭。而後將控制權限交給設備驅動程序,這樣就完畢了一個包的傳輸。
在Contiki中,可搶佔式多線程是在基於事件的內核之上以庫的形式實現的。庫是以可選的形式被連接到應用程序中的,即應用程序明白指出需要進行多線程模型操做的時候。纔將庫連接到應用程序中。庫被分爲兩類:與平臺獨立的部分——用於與事件內核相互做用。以及與平臺相關的部分——用於實現棧的切換和搶佔機制。一般,搶佔經過一個定時器中斷實現,保持處理器的寄存器到棧中,而後切換回內核棧。在實際中,當移植平臺相關部分庫時,僅僅需要重寫很是少的代碼。舉個樣例,對於MSP430平臺,僅僅用了25行C代碼。
與常規Contiki進程不一樣。每個線程都需要一個獨立的棧。
庫中提供了必要的棧管理函數。在線程明白地推出、或者被搶佔以前。每個線程在各自的棧上運行。
圖4.多線程庫的API
圖4展現了多線程庫中的API。它由六個函數(mt_yield(), mt_post(), mt_wait(), mt_exit(),mt_start() and mt_exec())組成。mt_exec()函數被事件處理器調用,用於運行實際的線程調度。
咱們已經用Contiki操做系統實現了很是多傳感器網絡應用程序。比方多跳路由、運動檢測。
咱們已經實現了一個簡單的空中編程協議。該協議使用點對點通訊。傳輸一個單一的二進制文件到選定的集線器。該二進制文件存儲在EEPROM,並在本節點接收到完整的程序時。被廣播到鄰居節點。
假設發生包丟失,可以由鄰居節點發出低電平信號做爲示意。而後由集線器節點修復。
咱們打算在未來實現更好的協議。比方Trickle算法。
咱們作了一個實驗,動態地分佈40個節點到報警系統中。咱們使用了空中重編程方法和人工有線重編程方法。最初。程序載入機制不是很無缺,在咱們的實驗中不能使用程序載入。咱們的目標程序大約爲6KB。加上Contiki核心和C庫,完整的系統鏡像接近30KB。使用人工有線重編程的方法,爲一個獨立的傳感器節點編程花費了超過30秒。而一共40個點檢,對整個網絡衝編程至少需要30分鐘。相反地。經過空中重編程的方法。僅僅使用了2分鐘。這減少了一個數量級。
表1.編譯後的代碼尺寸(字節)
對於一個爲受限設備開發的操做系統。代碼的尺寸和RAM的利用率必須足夠小,這樣才幹給執行在系統之上的應用留下足夠的空間。
表1展現了Contiki在兩個架構上的代碼尺寸和RAM使用率:TI MSP430和Atmel AVR。表中的數字翻譯了核心組件和應用案例——傳感器數據複製服務 的尺寸。
這個數據複製服務由服務接口根和和服務自身實現兩部分組成。眼下。程序載入器僅僅在MSP430平臺上實現了。
Contiki的代碼尺寸比TinyOS大,比Mantis系統小。由於提供了不一樣的服務。contiki的事件內核比TinyOS大是理所固然的。
TinyOS的事件內核僅提供了一個FIFO事件隊列調度器,而Contiki內核既支持FIFO事件,也支持優先級的輪訓處理器。而且,對於TinyOS這類系統而言,Contiki的靈活性也需要不少其它的執行時代碼。
RAM的需求依賴於系統配置的最大進程個數(p)。異步事件隊列的最大尺寸(e)以及多線程操做中的線程棧大小(s)。
圖5.在可搶佔計算期間響應時間有很是少的增量
搶佔式的目的是使長時間執行更有意義。能夠響應到來的事件,比方傳感器輸入或者到來的通訊數據包。
圖5描寫敘述了當Contiki正在執行一個需要8秒才幹完畢的可搶佔式線程是怎樣響應到來的通訊數據包的。曲線由200個往返ping包(每個包40直接)測試而來的。ping包的過程大約從第5秒開始到第13秒結束。
在這期間。往返的時間略微添加了但是依舊能夠響應ping包。
咱們的測試方法是每隔200ms,從1.4GHz的PC經過57600kb/s的速度向執行Contiki的ESB節點發送包。
數據包之因此經過串行線而不是無線鏈接,是爲了不無線電影響,比方位錯位或者MAC衝突。
咱們已經將Contiki移植到了很是多架構上。包含TI MSP430和Atmel AVR。其它人已經將系統一直到Hitachi SH3 和Zilog Z80。
移植過程包含寫啓動代碼、設備驅動、架構相關的程序載入器以及多線程庫中的棧切換代碼。內核和服務層不需要作不論什麼修改。
由於內核和服務層不需要不論什麼修改,第一個I/O驅動程序寫完後就可以進行實際的port測試了。Atem AVR的port移植由咱們本身完畢。包含有效的設備驅動程序,一共僅僅用了2小時。Zilog Z80的port移植由第三方完畢,也僅僅用了一天。
咱們已經描寫敘述了爲內存受限系統而設計的Contiki操做系統。爲了減少系統的大小。Contiki基於事件驅動內核。由於對於長時間執行的程序來講,很是難使用事件驅動系統中的狀態機編程。Contiki提供了執行時在事件驅動內核之上的搶佔式多線程應用庫,這個庫是可選地被連接到應用中,即當應用明白說明需要多線程模型時才幹連接。
一個正在執行的Contiki分爲兩部分:核心和可載入程序。核心由內核、一些類基本服務、語言執行時部分和支持的庫組成。載入程序可以在執行時被獨立地載入、卸載。
共享功能以共享庫的形式實現。服務可以被獨立更新或者替換,也添加了系統靈活性。 咱們可以看出,在保持基本系統輕量級和緊湊的同一時候。作到了在資源受限系統上靈活地載入、卸載程序和服務。雖然咱們的內核是基於事件驅動的。也爲應用程序提供了可搶佔式的多線程支持。 由於Contiki的動態特性,它可以被用於傳感器網絡的多硬件、多應用甚至多用戶。咱們將在支持安全機制方面繼續咱們的工做。