段鑫 陳宇 孫偉力
Lua是一種簡潔、輕量、可擴展的腳本語言。該語言的設計目的是為了嵌入宿主程序中,從而為應用程序提供靈活的擴展和定制功能。語言采用 clean C編寫(所謂 Clean C ,指 ANSI C 和 C++ 共通的一個子集),可移植性強,幾乎在所有操作系統和平臺上都可以編譯和運行,因此也可以運行在目前常見的嵌入式處理器上。通過Lua語言對應用軟件的擴展,實現了設備的現場可定制和可擴展能力,為嵌入式產品的應用提供了廣闊空間。Lua有內建與操作系統無關的協同模式,在Lua語言中稱之為coroutine。對于嵌入式產品的最終用戶,其關心的重點在具體應用功能的設計與實現上,采用協同的嵌入式功能擴展程序可以簡化開發過程。同時為避免過多的涉及程序設計語言細節,本文在實現協同功能的基礎上設計了適合嵌入式產品功能擴展的協同程序實現方法,簡化協同編程的實現。
Coroutine是Lua提供的一種“非對稱的協同程序”,即coroutine采用兩個函數來控制協同程序的執行,一個用于掛起執行,另一個用于恢復執行。一個非對稱協同程序可以看作是從屬于它的調用者,二者之間關系非常類似于例程與其調用者之間的關系。只是協同程序不是必須在執行邏輯結束時才返回到調用者,而可以在執行的任何階段主動掛起并返回調用者,當再次恢復執行時可以從掛起處繼續執行。
對于每個協同程序,在創建時都對應一個狀態(lua_State)或者稱之為線程(thread),狀態中保存了協同程序運行的上下文和數據。執行或恢復一個協同程序時,可以簡單的理解為從調用者狀態切換到了被執行協同程序的狀態上,當協同程序顯式的掛起自身時,則將當前狀態切換回調用者狀態。
由于Lua是嵌入式語言,語言本身提供了協同程序的各種操作,同時也提供了由宿主程序操作的API函數庫,庫中包含了協同程序的創建、恢復以及掛起操作的函數。因此可以通過宿主程序實現協同程序的創建和恢復操作,在lua程序中實現掛起操作,從而將整個協同程序的大部分操作封裝在宿主程序中執行,協同程序簡化為類似編寫普通Lua函數,同時又具備了協同程序的功能。
可以通過Lua本身提供的coroutine協同庫實現協同程序狀態的完全控制。其中 coroutine.create(f),coroutine.resume(co [, val1, ··])以及 coroutine.yield(…)三個函數用于創建和實現協同。當需要創建一個新的協同程序時,使用 coroutine.create函數 ,coroutine.create的唯一參數是函數,通常為匿名函數。例如:
Co = coroutine.create(
function(a, b)
for i=1,100 do
print(i)
coroutine.yield(a+i, b-i);
end
end
)
函數對于 Lua語言作為第一類值(First Class Value)看待,也就是函數可以存儲在變量中,可以通過參數傳遞給其他函數,還可以作為函數返回值。通過coroutine.create函數,將輸入的函數轉為參數,創建一個協同函數并賦值到Co變量,此時Co是處于掛起狀態的協同函數。
通過在程序中使用 coroutine.resume (co [, val1, ··])函數,恢復協同程序的執行。Coroutine.resume第一個參數為將要恢復執行的協同函數,其次為傳入的可變參數。例如執行上例中的協同程序可以通過 caller函數實現。
function caller ()
for i=0, 99 do
c,d,e = coroutine.resume(Co,10, 20)
print(c, d)
end
end
caller迭代的恢復Co執行,每次Co函數會執行一條print(i)語句,然后Co主動掛起自身,并將執行控制權交回 caller。對上例進行擴展,可以創建多個具有不同功能的協同函數,如:Co1、Co2等。caller根據需要每次恢復其中一個協同程序的執行,協同程序在每次執行后都將操作權返回給 caller,由 caller決定下一次執行,從而實現了多個協同程序之間的協同運行。由此可知,對于常規的協同處理,通常需要編寫多個協同處理函數,然后為每個協同處理函數創建協同狀態,最后在合適的程序中執行或恢復執行該協同函數。
在嵌入式開發中,基礎功能一般由宿主程序(應用程序)完成,如:設備驅動、任務調度、設備操作等,擴展功能則通過Lua程序實現,如:設備配置、邏輯處理等。對于具體的嵌入式應用,通常是可以確定擴展功能的種類和執行的條件,擴展功能采用Lua協同程序處理,宿主程序需要提供固定數量的協同程序以及可以由宿主程序決定在何種條件下創建及恢復執行協同程序的能力。

圖1 協同層次結構
固定數量的協同程序由宿主程序通過 API預先創建,并賦予唯一的函數名,同時指定參數。宿主程序預先創建所有協同程序的狀態(即實現協同程序的創建),也可以根據Lua程序中協同程序的使用情況動態創建狀態。宿主程序實現協同程序的參數傳遞,執行/恢復執行,協同程序死亡(Dead)后的再次執行等操作。
宿主程序創建了兩個與協同處理相關的任務協同執行任務(TaskScriptExec)和協同調度任務(TaskEventGen) 。其中 TaskScriptExec完成 Lua程序加載和協同程序的執行,TaskEventGen實現協同程序之間的調度。協同函數作為普通函數處理時(即函數本身不包含掛起操作),通常執行一次便處于死亡狀態,但是協同函數在具體應用中,條件符合時可能需要重復執行,因此 TaskEventGen還具有重新裝載協同函數使之可以再次運行的能力。
嵌入式系統提供的預置協同程序均為全局函數,在第一次加載Lua程序時,會將程序中使用到的全局函數注冊到全局變量表中,因此全局變量表中可能包含了預置的協同程序。宿主程序通過查詢該表判斷程序中是否存在相應的預置協同程序,如果存在則通過API函數創建該協同程序的狀態,反之則不做處理。
所有預置協同函數的執行都在同一個任務中,宿主程序根據調度結果,從協同隊列中獲取需要執行的協同函數。當該協同函數執行完或主動掛起后,控制權返回宿主程序,宿主程序會接著從隊列中獲取下一個協同函數執行。當隊列為空時,該任務處于空閑狀態。

圖2 協同執行流程
協同程序調度處理在適當條件下觸發需要執行的協同函數,并將該函數放入協同隊列中由協同函數執行任務完成執行操作。
通過對NXP公司的LPC2478 ARM7 處理器程序開發,對宿主程序的協同操作進行了實驗與測試。32位的處理器在72MHz的工作頻率下能夠很好的完成lua程序的執行。由于平臺不涉及GUI相關的功能,因此在操作系統的選擇上優先選擇微內核的輕量級實時操作系統,本實現基于uCOSII 3.84版本的操作系統。
宿主程序中預先實現了4個協同程序:
ON_EVENT1(a,b,c), ON_EVENT2(),
ON_EVENT4(), ON_SYSON()
協同程序的創建以及執行操作均由宿主程序完成,執行協同程序的條件通過串口接收字符1~4產生。協同程序的Lua測試代碼常用循環語句模擬要執行的任務。類似于如下代碼:
function aa()
for i=0,1000 do
print("in event2, i=",i)
coroutine.yield()
end
end
function ON_EVENT2()
local i=0
aa()
print("ON_EVENT2 end!")
end
當單個協同函數執行時,其運行就像執行普通函數,通過不斷的掛起和恢復操作直至執行結束,其測試運行結果如圖3所示。
當多個協同函數同時運行時,每次協同函數執行一段代碼后;當函數主動執行掛起操作后,便會將控制權交給宿主程序,宿主程序會將執行權調度給下一個協同函數,如此反復執行,測試運行結果如圖 4所示。圖中顯示了兩個協同函數ON_EVENT1與ON_EVENT2交替執行的情況。

圖3 單協同程序執行結果

圖4 多協同程序執行結果
本文采用的協同處理方法,使用戶在進行嵌入式產品的功能擴展時可以采用協同程序處理,實現了多個協同程序同時在一個任務中協同執行的能力,同時將協同處理的大部分工作在宿主程序中實現,很好的封裝了程序設計語言實現細節,簡化了應用開發難度,適合于領域內通用嵌入式模塊或產品的擴展。
[1] Robert Ierusalimschy. Programming in Lua, 2008.
[2] Ana L′ ucia de Moura. Coroutines in Lua, 2004.
[3] R. Ierusalimschy. Lua 5.1 Reference Manual, 2006.