時間觸發(fā)模式下的ProtothreadS設計應用
嵌入式行業(yè)的飛速發(fā)展使得嵌入式芯片的容量和功能都在不斷地提升。以工業(yè)應用最為常見的8位微控制器為例,其ROM和RAM的容量都在不斷擴大,甚至一些高端的8位處理器可以使用簡單的操作系統(tǒng)(OS)來進行開發(fā)。同時,32位處理器的廣泛普及也使得其價格逐漸逼近8位處理器。所以對很多應用來說,開發(fā)的簡化和迅速成為最主要的目標。尤其是當使用一款新的處理器或者硬件電路時,快速的搭建其開發(fā)的框架變得尤其重要。在嵌入式系統(tǒng)中,通常會采用兩種不同的任務管理和調度方式:事件觸發(fā)和時間觸發(fā)。事件觸發(fā)方式以事件的發(fā)生為核心,往往會采用多級中斷的方法來實現,其發(fā)生的時間具有隨機性。所以事件觸發(fā)方式具有較好的實時性,但是這樣也意味著該系統(tǒng)具有較高的復雜度。而且,事件觸發(fā)方式的開銷往往是很大的。Alexander Metzner的研究指出:一個包含27個任務、采用RM(Rate Mono—tonic)調度算法的事件觸發(fā)系統(tǒng),CPU的實際利用率僅為18%。
而時間觸發(fā)方式開發(fā)的系統(tǒng),保證了在同一時刻只處理一個事件,雖然犧牲了并發(fā)處理的實時性,但是保證了系統(tǒng)的可靠性,并且使得程序員能很好地預測程序的流程。Kopetz就指出:使用基于時間觸發(fā)的合作式調度器會使得系統(tǒng)有非常好的可預測性。在本項目中,嵌入式系統(tǒng)的功能主要集中在控制一些具有一定時隙間隔的外圍設備上面。鍵盤的掃描、顯示的刷新、數據緩沖的存儲等都是需要定期完成的任務,而這些任務的實時要求也并不高,所以選用時間觸發(fā)方式的合作式調度器成為系統(tǒng)設計的首選。
Protothrcads利用隱式的return提供了阻塞的功能,經過Protothrcads封裝的任務,其程序的邏輯更加接近處理事件的上層邏輯,大大簡化了編程。而且使用簡單的宏就可以實現Protothrcads,其開銷也是很小的。本文就對使用Protothrcads應用于時間觸發(fā)模式的合作式調度器做一些討論。
2 Protothreads簡介
Protothrcads是由瑞典計算機科學研究所的科學家Adam Dunkels所創(chuàng)的一種新的線程編程方法。按AdamDunkels所說,Protothreads是專為資源緊張的系統(tǒng)設計的一種耗費資源少,且不使用堆棧的線程模型,它可以不使用復雜的狀態(tài)機機制來實現順序流的控制。Proto—thrcads也可以用于操作系統(tǒng)當中。
簡單地說,Protothrcads借鑒了用c語言實現協(xié)同(co—routine)的原理,它應用switch—case語句的直接跳轉功能,實現了有條件阻塞(conditional block),最終實現了虛擬的并行處理功能(concurrent)。實際上,Protothrcads并不是真正的線程,在多任務的切換中并不會真正涉及上下文的切換,其線程的調度也僅僅是依靠隱式的return,進而退出函數體來完成的。但是Protothreads的優(yōu)點卻是實實在在的。首先它不需要堆??臻g,而正如筆者用宏實現的那樣,Protothrcads也實現了很多只有線程編程方法才能實現的機制,比如阻塞。而用宏進行了封裝之后,使用者完全可以像使用線程一樣使用它們,而且其邏輯更加簡化,這大大增加了程序的清晰度,并降低了開發(fā)維護的難度。
在對實時性要求比較高或者說要求并行處理的場合,往往需要在任務A執(zhí)行到一定程度、等待事件C發(fā)生時,退出當前任務A并轉而執(zhí)行任務B;當事件C發(fā)生之后,系統(tǒng)繼續(xù)回到任務A,繼續(xù)方才的執(zhí)行。所以必須將任務A上次執(zhí)行到地方的環(huán)境存儲起來,以便重回任務A后可以接著打斷的地方繼續(xù)運行。線程的上下文切換可以達到這個目的,Simon Tatham用C語言實現的co—routine也可以。Protothreads正是借鑒了這一原理,如以下任務函數所示:
可以看出,在進行了宏擴展之后,下面的程序段和上面的程序段是完全相同的,但是宏封裝很好地構建了一個上層的邏輯體系。這正是Protothreads的核心所在。同時,這也決定了Protothreads具有一定的局限:
①Protothreads中使用的必須是靜態(tài)變量或者全局變量;
②避免與switch語句的合用(Protothreads的實現已經用了switch語句);
③因為編譯器會將__LINE__解釋為當前所在的行號,所以不能將多個“返回”置于同一行。
3 調度器設計
時間觸發(fā)方式的嵌入式系統(tǒng)是根據定時器產生的恒定間隔的中斷來觸發(fā)和管理任務的。系統(tǒng)依靠一個基準的時間中斷,以此中斷為任務處理的節(jié)奏和“節(jié)拍”,任務的調度發(fā)生在“節(jié)拍”規(guī)定的時刻。中斷服務子程序也同樣占用這個時間間隔,為了系統(tǒng)的穩(wěn)定性,一方面要使中斷服務子程序盡可能短,以節(jié)省任務的執(zhí)行時間;另一方面,執(zhí)行的任務也應該盡可能短,一些比較耗時的任務可以細分為若干個子任務加以調度。同時,這也要求調度器的設計盡可能簡單。本文的設計思想就是對于系統(tǒng)僅僅定義一個任務控制塊(TCB)隊列,每個任務對應于隊列中的一個節(jié)點,由中斷服務子程序更改TCB隊列中的記錄,調度函數根據此TCB來進行調度??梢月晕⑿薷腜roto—threads中的結構體pt,以滿足作為TCB隊列節(jié)點的需要。當然,此結構也需要聲明為全局變量或者靜態(tài)變量。其數據結構如下所示:
按照Protothreads的定義,lc_t類型就是unsignedshort類型。每個任務分配一個pt結構。將pt結構修改以后,還必須對Protothreads提供的一些功能函數做一些修改。比如,可以將PT_INIT(&pt)更改為PT_INIT(&pt,10,0),表示該任務10 ms執(zhí)行一次,且ready的初始值為O。隊列的實現使用指針數組。
在時間觸發(fā)模式的系統(tǒng)中,定時器中斷作為系統(tǒng)一個固定的時間片,在具體實現中可以設置成CTC模式。這個時間片的選擇必須依據具體的應用,設置得過大會對系統(tǒng)調度的時效性造成比較大的影響,過小又會給調度器造成明顯的負擔,而且壓縮任務的執(zhí)行時間會使程序流程的可預測性受到影響。因為本文所涉及任務的周期大多是若干ms,所以可以將定時器中斷設置為1 ms。ISR的執(zhí)行流程大致如下:每一次定時中斷,將任務的count值減1,直到count為O時表明該任務的間隔時間已到可以執(zhí)行了,并且將初值重新賦給count,以重新開始下輪計數。具體程序如下:
調度函數快速輪詢各個任務的TCB。因為定時器中斷會定期更新任務的TCB信息,所以調度函數就可以根據TCB中ready的值來判斷是否需要執(zhí)行某任務。執(zhí)行任務過后清零該ready值。
如果任務task_XXX在執(zhí)行過程中發(fā)生中斷,ready值沒有被清零,待中斷返回后會繼續(xù)執(zhí)行之前的任務,但是這樣會使得下一時隙任務的執(zhí)行延遲,造成系統(tǒng)的安全隱患,所以應當盡量避免長任務的出現。而如果在任務執(zhí)行中出現條件阻塞(如PT_WAIT_UNTIL),則正好可以發(fā)揮Protothreads提供的并行處理能力,并且在處理類似鍵盤掃描的狀態(tài)機任務時具有很好的邏輯性和清晰度。當然,這樣做的前提是:這里的任務的實時性要求不高,允許出現一定的時延。
整個main()函數定義3個任務task_A、task_B和task_C,并且分別給每個任務分配一個結構體pt_A、pt_B和pt_C。3個任務的執(zhí)行周期分別是10 ms、15 ms和2ms。調度函數處于一個大循環(huán)中。具體實現如下所示:
4 總結和展望
Protothreads為嵌入式系統(tǒng)提供了很好的并行處理能力,而且非常易于操作;在時間觸發(fā)模式的系統(tǒng)中,Pro—tothreads依然能夠發(fā)揮其巨大的作用。在本文中筆者的設計很好地達到了實際的要求,最大程度上簡化了設計和維護。當然,應用Protothreads更加巧妙的設計方法和理念還需要不斷地實踐和總結。