段鑫 陳宇 孫偉力
Lua是一種簡潔、輕量、可擴展的腳本語言。該語言的設(shè)計目的是為了嵌入宿主程序中,從而為應(yīng)用程序提供靈活的擴展和定制功能。語言采用 clean C編寫(所謂 Clean C ,指 ANSI C 和 C++ 共通的一個子集),可移植性強,幾乎在所有操作系統(tǒng)和平臺上都可以編譯和運行,因此也可以運行在目前常見的嵌入式處理器上。通過Lua語言對應(yīng)用軟件的擴展,實現(xiàn)了設(shè)備的現(xiàn)場可定制和可擴展能力,為嵌入式產(chǎn)品的應(yīng)用提供了廣闊空間。Lua有內(nèi)建與操作系統(tǒng)無關(guān)的協(xié)同模式,在Lua語言中稱之為coroutine。對于嵌入式產(chǎn)品的最終用戶,其關(guān)心的重點在具體應(yīng)用功能的設(shè)計與實現(xiàn)上,采用協(xié)同的嵌入式功能擴展程序可以簡化開發(fā)過程。同時為避免過多的涉及程序設(shè)計語言細節(jié),本文在實現(xiàn)協(xié)同功能的基礎(chǔ)上設(shè)計了適合嵌入式產(chǎn)品功能擴展的協(xié)同程序?qū)崿F(xiàn)方法,簡化協(xié)同編程的實現(xiàn)。
Coroutine是Lua提供的一種“非對稱的協(xié)同程序”,即coroutine采用兩個函數(shù)來控制協(xié)同程序的執(zhí)行,一個用于掛起執(zhí)行,另一個用于恢復(fù)執(zhí)行。一個非對稱協(xié)同程序可以看作是從屬于它的調(diào)用者,二者之間關(guān)系非常類似于例程與其調(diào)用者之間的關(guān)系。只是協(xié)同程序不是必須在執(zhí)行邏輯結(jié)束時才返回到調(diào)用者,而可以在執(zhí)行的任何階段主動掛起并返回調(diào)用者,當(dāng)再次恢復(fù)執(zhí)行時可以從掛起處繼續(xù)執(zhí)行。
對于每個協(xié)同程序,在創(chuàng)建時都對應(yīng)一個狀態(tài)(lua_State)或者稱之為線程(thread),狀態(tài)中保存了協(xié)同程序運行的上下文和數(shù)據(jù)。執(zhí)行或恢復(fù)一個協(xié)同程序時,可以簡單的理解為從調(diào)用者狀態(tài)切換到了被執(zhí)行協(xié)同程序的狀態(tài)上,當(dāng)協(xié)同程序顯式的掛起自身時,則將當(dāng)前狀態(tài)切換回調(diào)用者狀態(tài)。
由于Lua是嵌入式語言,語言本身提供了協(xié)同程序的各種操作,同時也提供了由宿主程序操作的API函數(shù)庫,庫中包含了協(xié)同程序的創(chuàng)建、恢復(fù)以及掛起操作的函數(shù)。因此可以通過宿主程序?qū)崿F(xiàn)協(xié)同程序的創(chuàng)建和恢復(fù)操作,在lua程序中實現(xiàn)掛起操作,從而將整個協(xié)同程序的大部分操作封裝在宿主程序中執(zhí)行,協(xié)同程序簡化為類似編寫普通Lua函數(shù),同時又具備了協(xié)同程序的功能。
可以通過Lua本身提供的coroutine協(xié)同庫實現(xiàn)協(xié)同程序狀態(tài)的完全控制。其中 coroutine.create(f),coroutine.resume(co [, val1, ··])以及 coroutine.yield(…)三個函數(shù)用于創(chuàng)建和實現(xiàn)協(xié)同。當(dāng)需要創(chuàng)建一個新的協(xié)同程序時,使用 coroutine.create函數(shù) ,coroutine.create的唯一參數(shù)是函數(shù),通常為匿名函數(shù)。例如:
Co = coroutine.create(
function(a, b)
for i=1,100 do
print(i)
coroutine.yield(a+i, b-i);
end
end
)
函數(shù)對于 Lua語言作為第一類值(First Class Value)看待,也就是函數(shù)可以存儲在變量中,可以通過參數(shù)傳遞給其他函數(shù),還可以作為函數(shù)返回值。通過coroutine.create函數(shù),將輸入的函數(shù)轉(zhuǎn)為參數(shù),創(chuàng)建一個協(xié)同函數(shù)并賦值到Co變量,此時Co是處于掛起狀態(tài)的協(xié)同函數(shù)。
通過在程序中使用 coroutine.resume (co [, val1, ··])函數(shù),恢復(fù)協(xié)同程序的執(zhí)行。Coroutine.resume第一個參數(shù)為將要恢復(fù)執(zhí)行的協(xié)同函數(shù),其次為傳入的可變參數(shù)。例如執(zhí)行上例中的協(xié)同程序可以通過 caller函數(shù)實現(xiàn)。
function caller ()
for i=0, 99 do
c,d,e = coroutine.resume(Co,10, 20)
print(c, d)
end
end
caller迭代的恢復(fù)Co執(zhí)行,每次Co函數(shù)會執(zhí)行一條print(i)語句,然后Co主動掛起自身,并將執(zhí)行控制權(quán)交回 caller。對上例進行擴展,可以創(chuàng)建多個具有不同功能的協(xié)同函數(shù),如:Co1、Co2等。caller根據(jù)需要每次恢復(fù)其中一個協(xié)同程序的執(zhí)行,協(xié)同程序在每次執(zhí)行后都將操作權(quán)返回給 caller,由 caller決定下一次執(zhí)行,從而實現(xiàn)了多個協(xié)同程序之間的協(xié)同運行。由此可知,對于常規(guī)的協(xié)同處理,通常需要編寫多個協(xié)同處理函數(shù),然后為每個協(xié)同處理函數(shù)創(chuàng)建協(xié)同狀態(tài),最后在合適的程序中執(zhí)行或恢復(fù)執(zhí)行該協(xié)同函數(shù)。
在嵌入式開發(fā)中,基礎(chǔ)功能一般由宿主程序(應(yīng)用程序)完成,如:設(shè)備驅(qū)動、任務(wù)調(diào)度、設(shè)備操作等,擴展功能則通過Lua程序?qū)崿F(xiàn),如:設(shè)備配置、邏輯處理等。對于具體的嵌入式應(yīng)用,通常是可以確定擴展功能的種類和執(zhí)行的條件,擴展功能采用Lua協(xié)同程序處理,宿主程序需要提供固定數(shù)量的協(xié)同程序以及可以由宿主程序決定在何種條件下創(chuàng)建及恢復(fù)執(zhí)行協(xié)同程序的能力。
圖1 協(xié)同層次結(jié)構(gòu)
固定數(shù)量的協(xié)同程序由宿主程序通過 API預(yù)先創(chuàng)建,并賦予唯一的函數(shù)名,同時指定參數(shù)。宿主程序預(yù)先創(chuàng)建所有協(xié)同程序的狀態(tài)(即實現(xiàn)協(xié)同程序的創(chuàng)建),也可以根據(jù)Lua程序中協(xié)同程序的使用情況動態(tài)創(chuàng)建狀態(tài)。宿主程序?qū)崿F(xiàn)協(xié)同程序的參數(shù)傳遞,執(zhí)行/恢復(fù)執(zhí)行,協(xié)同程序死亡(Dead)后的再次執(zhí)行等操作。
宿主程序創(chuàng)建了兩個與協(xié)同處理相關(guān)的任務(wù)協(xié)同執(zhí)行任務(wù)(TaskScriptExec)和協(xié)同調(diào)度任務(wù)(TaskEventGen) 。其中 TaskScriptExec完成 Lua程序加載和協(xié)同程序的執(zhí)行,TaskEventGen實現(xiàn)協(xié)同程序之間的調(diào)度。協(xié)同函數(shù)作為普通函數(shù)處理時(即函數(shù)本身不包含掛起操作),通常執(zhí)行一次便處于死亡狀態(tài),但是協(xié)同函數(shù)在具體應(yīng)用中,條件符合時可能需要重復(fù)執(zhí)行,因此 TaskEventGen還具有重新裝載協(xié)同函數(shù)使之可以再次運行的能力。
嵌入式系統(tǒng)提供的預(yù)置協(xié)同程序均為全局函數(shù),在第一次加載Lua程序時,會將程序中使用到的全局函數(shù)注冊到全局變量表中,因此全局變量表中可能包含了預(yù)置的協(xié)同程序。宿主程序通過查詢該表判斷程序中是否存在相應(yīng)的預(yù)置協(xié)同程序,如果存在則通過API函數(shù)創(chuàng)建該協(xié)同程序的狀態(tài),反之則不做處理。
所有預(yù)置協(xié)同函數(shù)的執(zhí)行都在同一個任務(wù)中,宿主程序根據(jù)調(diào)度結(jié)果,從協(xié)同隊列中獲取需要執(zhí)行的協(xié)同函數(shù)。當(dāng)該協(xié)同函數(shù)執(zhí)行完或主動掛起后,控制權(quán)返回宿主程序,宿主程序會接著從隊列中獲取下一個協(xié)同函數(shù)執(zhí)行。當(dāng)隊列為空時,該任務(wù)處于空閑狀態(tài)。
圖2 協(xié)同執(zhí)行流程
協(xié)同程序調(diào)度處理在適當(dāng)條件下觸發(fā)需要執(zhí)行的協(xié)同函數(shù),并將該函數(shù)放入?yún)f(xié)同隊列中由協(xié)同函數(shù)執(zhí)行任務(wù)完成執(zhí)行操作。
通過對NXP公司的LPC2478 ARM7 處理器程序開發(fā),對宿主程序的協(xié)同操作進行了實驗與測試。32位的處理器在72MHz的工作頻率下能夠很好的完成lua程序的執(zhí)行。由于平臺不涉及GUI相關(guān)的功能,因此在操作系統(tǒng)的選擇上優(yōu)先選擇微內(nèi)核的輕量級實時操作系統(tǒng),本實現(xiàn)基于uCOSII 3.84版本的操作系統(tǒng)。
宿主程序中預(yù)先實現(xiàn)了4個協(xié)同程序:
ON_EVENT1(a,b,c), ON_EVENT2(),
ON_EVENT4(), ON_SYSON()
協(xié)同程序的創(chuàng)建以及執(zhí)行操作均由宿主程序完成,執(zhí)行協(xié)同程序的條件通過串口接收字符1~4產(chǎn)生。協(xié)同程序的Lua測試代碼常用循環(huán)語句模擬要執(zhí)行的任務(wù)。類似于如下代碼:
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
當(dāng)單個協(xié)同函數(shù)執(zhí)行時,其運行就像執(zhí)行普通函數(shù),通過不斷的掛起和恢復(fù)操作直至執(zhí)行結(jié)束,其測試運行結(jié)果如圖3所示。
當(dāng)多個協(xié)同函數(shù)同時運行時,每次協(xié)同函數(shù)執(zhí)行一段代碼后;當(dāng)函數(shù)主動執(zhí)行掛起操作后,便會將控制權(quán)交給宿主程序,宿主程序會將執(zhí)行權(quán)調(diào)度給下一個協(xié)同函數(shù),如此反復(fù)執(zhí)行,測試運行結(jié)果如圖 4所示。圖中顯示了兩個協(xié)同函數(shù)ON_EVENT1與ON_EVENT2交替執(zhí)行的情況。
圖3 單協(xié)同程序執(zhí)行結(jié)果
圖4 多協(xié)同程序執(zhí)行結(jié)果
本文采用的協(xié)同處理方法,使用戶在進行嵌入式產(chǎn)品的功能擴展時可以采用協(xié)同程序處理,實現(xiàn)了多個協(xié)同程序同時在一個任務(wù)中協(xié)同執(zhí)行的能力,同時將協(xié)同處理的大部分工作在宿主程序中實現(xiàn),很好的封裝了程序設(shè)計語言實現(xiàn)細節(jié),簡化了應(yīng)用開發(fā)難度,適合于領(lǐng)域內(nèi)通用嵌入式模塊或產(chǎn)品的擴展。
[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.