使用Protothreads設計嵌入式多任務(wù)程序
張 晉
(青島科技大學(xué) 信息科學(xué)技術(shù)學(xué)院,山東 青島 266061)
摘 要 針對嵌入式多任務(wù)程序設計所面對的問(wèn)題,探討了該類(lèi)程序的一般設計方法。分析了使用Protothreads方法設計嵌入式多任務(wù)程序的獨特優(yōu)勢。通過(guò)實(shí)例展現了Protothreads的應用,并簡(jiǎn)要分析了Protothreads的局限性。
關(guān)鍵詞 嵌入式系統;多任務(wù);事件驅動(dòng);Protothreads
1 引言
目前嵌入式系統的功能日益復雜。受到設計難度、成本等諸多因素的要求,在單片機系統等低成本而又資源緊缺型的嵌入式系統中,設計簡(jiǎn)潔、穩定且易于調試的多任務(wù)程序,已經(jīng)成為十分重要的軟件技術(shù)。
Protothreads是一種針對C語(yǔ)言封裝后的宏函數庫,為C語(yǔ)言模擬了一種無(wú)堆棧的輕量線(xiàn)程環(huán)境,能夠實(shí)現模擬線(xiàn)程的條件阻塞、信號量操作等操作系統中特有的機制,從而使程序實(shí)現多線(xiàn)程操作。每個(gè)Protothreads線(xiàn)程僅增加10行代碼和2字節RAM的額外硬件資源消耗[1]。對于資源緊缺而不能移植嵌入式操作系統的嵌入式系統,使用Protothreads能夠方便直觀(guān)地設計多任務(wù)程序,能夠實(shí)現用線(xiàn)性程序結構處理事件驅動(dòng)型程序和狀態(tài)機程序,簡(jiǎn)化了該類(lèi)程序的設計[2]。
2 嵌入式多任務(wù)程序常用的的設計方法
常見(jiàn)的嵌入式多任務(wù)程序的設計方法有死循環(huán)方法、狀態(tài)機方法、嵌入式操作系統方法等。這些方法各有各自的優(yōu)缺點(diǎn),并且均在不同的場(chǎng)合得到了廣泛的應用。
2.1 死循環(huán)方法
死循環(huán)方法是最常用的多任務(wù)處理方法。其程序結構通常如下:
While(1) {
if( condition1 )
task1();
if( condition2 )
task2();
……
}
實(shí)際程序中往往是用中斷或其他任務(wù)來(lái)觸發(fā)各個(gè)任務(wù)的執行條件。該方法思路簡(jiǎn)潔,沒(méi)有多余的硬件資源消耗,程序的設計和調試都十分便捷。但這種的缺點(diǎn)很明顯:程序無(wú)法保證各個(gè)任務(wù)的并發(fā)執行。如果循環(huán)中某個(gè)任務(wù)需要長(cháng)時(shí)間等待,在這種結構中無(wú)法將其暫時(shí)阻塞,從而導致其他任務(wù)的執行都受到影響。如果程序中有任務(wù)有實(shí)時(shí)性要求,顯然這種程序結構是難以滿(mǎn)足的[3]。
2.2 狀態(tài)機方法
有限狀態(tài)機(FSM)方法是一種重要的程序設計方法,有成熟的理論基礎。在事件驅動(dòng)型程序設計以及硬件描述語(yǔ)言程序(如VHDL,Verilog等)設計中,該方法有廣泛的應用,而且有眾多軟件開(kāi)發(fā)工具的支持。
該方法需要事先建立程序狀態(tài)轉換圖,根據狀態(tài)轉換圖在程序中通過(guò)判斷狀態(tài)標志變量的狀態(tài),引導程序在各個(gè)任務(wù)之間跳轉,從而實(shí)現多任務(wù)程序。在C語(yǔ)言中,該方法往往通過(guò)switch-case的程序結構來(lái)實(shí)現。同時(shí),通過(guò)隨時(shí)向指定的任務(wù)跳轉,該程序結構也能夠實(shí)現類(lèi)似于程序阻塞的操作,讓實(shí)時(shí)性要求更高的程序得以及時(shí)執行。狀態(tài)機程序同樣不會(huì )引入過(guò)多的硬件資源消耗,很適合嵌入式環(huán)境。
該方法的缺點(diǎn)主要在于程序狀態(tài)較多的時(shí)候,程序狀態(tài)圖較難建立。此外狀態(tài)機程序往往是一種網(wǎng)狀的程序結構,程序的設計、維護和調試與其他方法相比難度較大[2]。
2.3 嵌入式操作系統方法
嵌入式操作系統為嵌入式環(huán)境的多任務(wù)設計提供了完善的解決方法。無(wú)論是面向低端硬件設備的簡(jiǎn)單系統,例如uC/OS-II、Contiki、FreeRTOS等;還是面向高端硬件的例如Windows CE、嵌入式Linux等復雜嵌入式操作系統;均提供了完整的進(jìn)程(線(xiàn)程)環(huán)境、消息郵箱、信號量等機制,以及相應的任務(wù)調度器。能夠十分方便的進(jìn)行多任務(wù)程序設計,同時(shí)通過(guò)各種調度算法滿(mǎn)足各種不同優(yōu)先級任務(wù)的實(shí)時(shí)性要求。
但該方法也有很多約束性:首先是對硬件資源要求很高,至少是數十KB的RAM以及數百KB的ROM,復雜嵌入式系統甚至要求數MB的RAM和數十MB的ROM,這將帶來(lái)硬件成本巨大上升,但實(shí)際應用中很多場(chǎng)合并不需要這么高端的硬件資源。同時(shí),復雜的軟硬件環(huán)境使得軟硬件的設計過(guò)程過(guò)于復雜和專(zhuān)業(yè),設計周期也因此變長(cháng)。此外,嵌入式操作系統多數是商業(yè)軟件,授權費用昂貴,而且硬件上往往需要一系列與之配套的芯片,高昂的成本使得這種方法對于簡(jiǎn)單嵌入式設備并不適合。
3 使用Protothreads方法編寫(xiě)嵌入式多任務(wù)程序
3.1 Protothreads簡(jiǎn)介
Protothreads是由瑞典計算機科學(xué)研究院的Adam Dunkels編寫(xiě)的基于C語(yǔ)言的宏函數庫,通過(guò)該宏函數庫為C語(yǔ)言模擬了一種輕量級的無(wú)堆棧線(xiàn)程環(huán)境,從而實(shí)現類(lèi)似多線(xiàn)程的程序。
在Protothreads庫中,PT_INIT()、PT_THREAD()操作用來(lái)初始化和聲明一個(gè)Protothreads線(xiàn)程,PT_BEGIN()、PT_END()操作用來(lái)開(kāi)始和結束一個(gè)Protothreads線(xiàn)程,PT_WAIT_UNTIL()、PT_WAIT_WHILE()操作用來(lái)實(shí)現Protothreads線(xiàn)程的條件阻塞或等待,PT_SEM_INIT()、PT_SEM_SIGNAL()、PT_SEM_WAIT()用于實(shí)現Protothreads線(xiàn)程信號量的各種操作,此外還有線(xiàn)程的派生、重啟等操作[4]。
Protothreads使用ANSI C編寫(xiě),可以在不同的編譯器間方便的移植,在工程中加入Protothreads的源碼,并在相應的程序中引用其頭文件即可。Protothreads引入的額外硬件資源消耗極小,十分方便在資源緊缺的小型嵌入式系統中使用。尤其對于事件驅動(dòng)型程序,使用Protothreads能使代碼結構更加清晰、線(xiàn)性化。Protothreads能夠有效地替代狀態(tài)機方法,或者輔助減少程序的狀態(tài)個(gè)數,簡(jiǎn)化狀態(tài)機程序的編寫(xiě)。
Protothreads是開(kāi)源軟件,鑒于其種種優(yōu)勢,Protothreads在許多著(zhù)名的軟件項目中得到廣泛應用,例如嵌入式操作系統Contiki和嵌入式TCP/IP協(xié)議棧uIP[2]。
3.2 應用實(shí)例
以下使用一個(gè)數字鐘程序來(lái)展示Protothreads的基本用法,在該程序中,時(shí)、分、秒的計數分別采用三個(gè)Protothreads線(xiàn)程來(lái)完成。該程序在PC機上運行檢驗,直接使用系統函數sleep()來(lái)計時(shí)一秒鐘,在實(shí)際的嵌入式系統中,可以使用相應的硬件定時(shí)器來(lái)實(shí)現。主函數如下:
/*包含pt.h頭文件后即可使用Protothreads*/
#include <stdio.h>
#include <windows.h>
#include "pt.h"
/*由于Protothreads線(xiàn)程沒(méi)有各自的堆棧,
所以需要靜態(tài)變量來(lái)保存各個(gè)線(xiàn)程的參數*/
static struct pt pt_h,pt_m,pt_s;
/*分別用于存儲時(shí)、分、秒的計數值*/
static int ihour=0,iminute=0,isecond=0;
/*分鐘和秒鐘的進(jìn)位標志位*/
static int c_s=0,c_m=0;
/*用于模擬秒鐘的標志位*/
static int clk=0;
int main(void) {
/*分別初始化三個(gè)線(xiàn)程*/
PT_INIT(&pt_h);
PT_INIT(&pt_m);
PT_INIT(&pt_s);
while(1) {
/*循環(huán)調用三個(gè)線(xiàn)程*/
PT_SCHEDULE( second(&pt_s) );
PT_SCHEDULE( minute(&pt_m) );
PT_SCHEDULE( hour(&pt_h) );
/*使用系統函數來(lái)計數一秒鐘*/
sleep(1);
clk = 1;
/*輸出時(shí)間*/
printf("Time --- %d:%d:%d\n",ihour,iminute,isecond);
}
return 0;
}
分別實(shí)現時(shí)、分、秒計數的三個(gè)Protothreads線(xiàn)程如下:
/*計數小時(shí)的線(xiàn)程*/
PT_THREAD( hour( struct pt * pt ) ) {
/*線(xiàn)程的開(kāi)始*/
PT_BEGIN(pt);
/*Protothreads的條件阻塞機制,這里用于等待分鐘的進(jìn)位*/
PT_WAIT_UNTIL( pt,c_m==1 );
/*上面的條件滿(mǎn)足時(shí),下面的代碼才得以運行*/
c_m = 0;
if(ihour == 23)
ihour = 0;
else
ihour++;
/*線(xiàn)程的結束*/
PT_END(pt);
}
/*計數分鐘的線(xiàn)程*/
PT_THREAD( minute( struct pt * pt ) ) {
PT_BEGIN(pt);
/*條件阻塞,等待秒鐘的進(jìn)位*/
PT_WAIT_UNTIL( pt,c_s==1 );
/*上面的條件滿(mǎn)足時(shí),下面的代碼才得以運行*/
c_s = 0;
if(iminute == 59) {
c_m = 1;
iminute = 0;
}
else
iminute ++;
PT_END(pt);
}
/*計數秒鐘的線(xiàn)程*/
PT_THREAD( second(struct pt * pt) ) {
PT_BEGIN(pt);
/*條件阻塞,等待秒鐘信號的發(fā)生*/
PT_WAIT_UNTIL( pt,clk==1 );
/*上面的條件滿(mǎn)足時(shí),下面的代碼才得以運行*/
clk = 0;
if(isecond == 59) {
isecond = 0;
c_s = 1;
}
else
isecond++;
PT_END(pt);
}
該程序經(jīng)測試運行正確。由該程序可以看出,使用Protothreads線(xiàn)程可以讓不滿(mǎn)足運行條件的線(xiàn)程自我阻塞,從而讓其他線(xiàn)程得以及時(shí)執行。程序的結構清晰化,使得多任務(wù)處理、條件阻塞等機制在一般地程序中得以實(shí)現。
4 Protothreads的局限性
雖然Protothreads具有占用資源少、方便多任務(wù)處理等優(yōu)點(diǎn),但由于其本質(zhì)上僅僅是對一些C程序的封裝,并不是真正的線(xiàn)程。所以與真正的操作系統線(xiàn)程相比,Protothreads線(xiàn)程具有一些局限性[1]:
(1)Protothreads線(xiàn)程不具備各自的堆棧,而是采用所有線(xiàn)程共享主程序的堆棧的方式。這雖然能夠極大地減少硬件資源的消耗,但也使得每個(gè)Protothreads線(xiàn)程無(wú)法保存各自的局部變量,而必須通過(guò)靜態(tài)變量來(lái)保存必要的數據。
(2)Protothreads雖然提供了在各自線(xiàn)程內的條件阻塞機制,但對于在該線(xiàn)程內調用的其它函數,則無(wú)法阻塞其運行。所以,如果要在線(xiàn)程內調用占用時(shí)間較多的函數,為保證各個(gè)線(xiàn)程的實(shí)時(shí)性要求,需要將這類(lèi)函數進(jìn)一步劃分為更小的函數,分步執行。
(3)使用ANSI C實(shí)現的Protothreads庫采用了封裝switch-case語(yǔ)句的方法,因此,按照C語(yǔ)言語(yǔ)法的要求,Protothreads不能與switch-case語(yǔ)句混用。但是,Protothreads庫的另一種實(shí)現方式是采用了GCC編譯器的一些擴展特性,這樣的Protothreads庫能夠克服該缺點(diǎn),但具有編譯器的依賴(lài)性。
5 結束語(yǔ)
本文通過(guò)與一般嵌入式多任務(wù)程序設計方法的對比,分析了Protothreads在設計嵌入式多任務(wù)程序方面的優(yōu)勢,通過(guò)一個(gè)實(shí)例展示了Protothreads的基本使用方法,并分析了Protothreads的局限性。Protothreads為嵌入式環(huán)境的多任務(wù)程序設計和事件驅動(dòng)程序設計提供了一種有效地處理方法,使得程序的設計、維護和調試更加便捷,對于嵌入式軟件開(kāi)發(fā)有較大的參考價(jià)值。
參考文獻
[1] Adam Dunkels,Oliver Schmidt. Lightweight,Stackless Threads in C[R]. Sweden:Swedish Institute of Computer Science,2005
[2] Adam Dunkels,Oliver Schmidt,Thiemo Voigt. Protothreads:Simplifying Event - Driven Programming of Memory -Constrained Embedded Systems. Proceedings of the Fourth ACM Conference on Embedded Networked Sensor Systems,2006[C]. Boulder,Colorado,USA,November 2006
[3] 羅光平,郭衛鋒.利用Protothread實(shí)現實(shí)時(shí)多任務(wù)系統[J].單片機與嵌入式系統應用,2008(5):32-35
[4] Adam Dunkels. The Protothreads Library 1.4 Reference Manual[M/OL]. http://www.sics.se/~adam/pt/publications.html
收稿日期:5 月 18 日 修改日期:6 月 24 日
作者簡(jiǎn)介:張晉(1984-),男,碩士研究生,研究方向:嵌入式系統應用。