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