Lua語(yǔ)言可研究的東西真是多,各種機(jī)制原理:與宿主語(yǔ)言(下文均指C/C++)的交互、內(nèi)存管理(垃圾回收)、虛擬機(jī)實(shí)現(xiàn)、協(xié)程、閉包、異常捕獲機(jī)制等。如取其一進(jìn)行研究,要吃透還是需要點(diǎn)時(shí)間和精力。相信只要一點(diǎn)點(diǎn)慢慢啃,終究還是會(huì)將其吸收。
以下的相關(guān)原理介紹是基于Lua-5.1.5版本的源碼,不排除與之后版本的源碼中有少部分差異存在,但基本原理應(yīng)該相同。
lua_State是Lua語(yǔ)言中的一種基本類型,類似TString,Table等,主要用來管理一個(gè)lua虛擬機(jī)的執(zhí)行環(huán)境,一個(gè)lua虛擬機(jī)可以有多個(gè)執(zhí)行環(huán)境,lua_State最主要的功能就是用于函數(shù)調(diào)用以及和C/C++的交互。
主要功能包括:
1. 數(shù)據(jù)棧管理,包括交互過程中參數(shù)壓棧和出棧、函數(shù)注冊(cè)的臨時(shí)數(shù)據(jù)存儲(chǔ)等。
2. 調(diào)用棧管理,其中CallInfo結(jié)構(gòu)表示一次調(diào)用,包括指向數(shù)據(jù)棧中數(shù)據(jù)邊界指針top和base、被調(diào)用函數(shù)指針func。
3. 全局表l_gt管理,注意:它其實(shí)只是在當(dāng)前l(fā)ua_State范圍內(nèi)是全局唯一的,和global_State的l_registry注冊(cè)表不同,l_registry是lua虛擬機(jī)范圍內(nèi)是全局唯一的。
4. gc的一些管理和當(dāng)前棧中upvalue的管理(出現(xiàn)閉包應(yīng)用場(chǎng)景時(shí))。
5. hook相關(guān)的,包括hookmask,hookcount,hook函數(shù)等(暫未了解)。
以上1、2點(diǎn)是Lua與C/C++交互時(shí)操作最頻繁的步驟,因此整理數(shù)據(jù)棧和調(diào)用棧的操作流程是理解交互的重中之重。
Lua 虛擬機(jī)全局狀態(tài)的存儲(chǔ)的地方,管理lua虛擬機(jī)的全局環(huán)境,所有的lua_State共享這個(gè)全局狀態(tài),由于Lua被設(shè)計(jì)為單線程的,所以global_State上的狀態(tài)控制沒有考慮多線程問題。主要功能可分為:內(nèi)存分配策略、全局字符串hashtable管理、注冊(cè)表、gc管理、lua_State集合管理、元表管理(暫時(shí)尚未知用來干嘛)等。
最后記住一條:一個(gè)Lua虛擬機(jī)有且只有一個(gè)global_State對(duì)象,一個(gè)進(jìn)程中允許多個(gè)Lua虛擬機(jī)同時(shí)運(yùn)行。運(yùn)行形態(tài)例如下圖所示:

Lua的API中提供了宏lua_open()用于啟動(dòng)一個(gè)Lua虛擬機(jī),之所以起了這個(gè)名,估計(jì)是為了與lua_close()對(duì)應(yīng),open、close看著就知道明顯是一對(duì)。
啟動(dòng)Lua虛擬機(jī)的過程總結(jié)成一句話就是:構(gòu)造global_State、lua_State對(duì)象,并初始化。具體包括以下幾個(gè)步驟:
1、通過內(nèi)存分配策略(l_alloc)分配兩個(gè)對(duì)象(global_State、lua_State)大小的內(nèi)存空間。
2、初始化lua_State對(duì)象,例如:類型設(shè)置、數(shù)據(jù)??臻g分配(45*TValue)、調(diào)用??臻g分配(8*CallInfo)等。
3、初始化global_State對(duì)象,例如:主lua_State設(shè)置、內(nèi)存分配方法、全局字符串表等。
4、加載所有標(biāo)準(zhǔn)庫(kù)到Lua虛擬機(jī)執(zhí)行環(huán)境中,實(shí)際操作就是將各種庫(kù)提供庫(kù)函數(shù)通過現(xiàn)有的注冊(cè)機(jī)制注冊(cè)到當(dāng)前l(fā)ua_State的l_gt中,之后Lua腳本中就可以直接調(diào)用注冊(cè)過的庫(kù)函數(shù)。
Lua現(xiàn)在支持的庫(kù)有:協(xié)程庫(kù)、表操作庫(kù)、io庫(kù)、系統(tǒng)庫(kù)、string庫(kù)、math庫(kù)、debug庫(kù)、包處理庫(kù),以string庫(kù)為例:
static const luaL_Reg strlib[] =
{
{"byte", str_byte},
{"char", str_char},
{"dump", str_dump},
{"find", str_find},
{"format", str_format},
{"gmatch", gmatch},
{"gsub", str_gsub},
{"len", str_len},
{"lower", str_lower},
{"match", str_match},
{"rep", str_rep},
{"reverse", str_reverse},
{"sub", str_sub},
{"upper", str_upper},
{"pack", str_pack},
{"packsize", str_packsize},
{"unpack", str_unpack},
{NULL, NULL}
};
luaL_register(L, LUA_STRLIBNAME, strlib); //注冊(cè)函數(shù)
經(jīng)過以上若干步驟后,lua虛擬機(jī)執(zhí)行環(huán)境已準(zhǔn)備就緒,宿主語(yǔ)言就可以和lua腳本進(jìn)行相互調(diào)用。
與宿主語(yǔ)言交互時(shí)的棧主要涉及兩個(gè),為了方便理解,暫且將它歸納為:DataStack和CallStack,其中棧操作指針變量均是lua_State的成員,DataStack可以理解為交互數(shù)據(jù)的實(shí)際存儲(chǔ)地,而CallStack中記錄著每次調(diào)用需要的數(shù)據(jù)在DataStack中的地址范圍,具體如下:
// DataStack
typedef Tvalue*StkId;
StkId top; /* first free slot in the stack (棧頂指針) */
StkId base; /* base of current function (當(dāng)前調(diào)用幀所在DataStack中的棧底) */
StkIdstack_last; /* last free slot in thestack (??臻g的上邊界) */
StkIdstack; /* stack base (棧空間的下邊界,即棧底)*/
// CallStack
CallInfo*ci; /* call info for current function (當(dāng)前調(diào)用幀)*/
CallInfo *end_ci; /* points after end of ci array (調(diào)用棧空間的上邊界)*/
CallInfo*base_ci; /* array of CallInfo's (調(diào)用??臻g的下邊界,即棧底)*/
根據(jù)以上分類,清楚可知:在Lua和宿主語(yǔ)言交互時(shí),實(shí)際就是這些指向棧的指針不停被更新的過程。下圖描述了Lua虛擬機(jī)啟動(dòng)后,兩個(gè)棧原始狀態(tài)的直觀感覺:

在了解完以上所有前提知識(shí)點(diǎn)后,從一次C++調(diào)用Lua方法的示例入手理解,逐步深入其中的棧操作過程,調(diào)用示例如下:
lua_settop(L,0);
lua_getglobal(L,“l(fā)ua_Function”); //假設(shè)lua腳本中有一個(gè)名為lua_Function的方法
if( lua_pcall(L, 0, LUA_MULTERT, 0) )
{
/* 調(diào)用錯(cuò)誤,從(L->top) + index的棧中取出錯(cuò)誤信息,然后pop掉該棧幀*/
}
else
{
/* 調(diào)用正確,從(L->base) +(index – 1)的棧中取出返回信息,然后pop掉該棧幀 */
}
分解步驟:
(1)、lua_settop(L,0): 調(diào)整DataStack中top、base兩個(gè)指向棧的指針值,使L->top== L->base,指向統(tǒng)一棧幀位置,屬于調(diào)用的前期準(zhǔn)備。
(2)、lua_getglobal(L,“l(fā)ua_Function”):獲取lua腳本中被調(diào)用方法“l(fā)ua_Function”,并將其壓入DataStack,此時(shí)L->base指向剛?cè)霔?,L->top + 1。檢索“l(fā)ua_Function”的步驟:
1). 計(jì)算“l(fā)ua_Function”字符串的哈希值,然后與global_state的stringtable中匹配出該哈希值下的所有字符串對(duì)象所在的沖突鏈表,遍歷鏈表查詢是否存在“l(fā)ua_Function”,存在則直接返回值,否則臨時(shí)構(gòu)造TString對(duì)象并返回;
2).根據(jù)返回的字符串對(duì)象從lua_State的l_gt(所有函數(shù)的注冊(cè)地)中匹配出該函數(shù)名對(duì)
應(yīng)的函數(shù)對(duì)象;
3). 將以上檢索出來的函數(shù)對(duì)象壓入DataStack。
(3)、如果調(diào)用的lua_Function方法有參數(shù),則繼續(xù)向DataStack壓棧,L->top+ nArgs,nArgs為參數(shù)個(gè)數(shù),L->base則繼續(xù)指向被調(diào)用方法lua_Function所在的棧位置。
(4)、lua_pcall(L,nArgs, LUA_MULTERT, nRets):開始調(diào)用lua方法,在到達(dá)真正調(diào)用那一步前,需要有以下子操作:
1). 計(jì)算此前入棧的lua_Function所在棧位置:func=L->top–(nArgs+1),為何不直接
用L->base?
2). 更新L->base的值,L->base = func+ 1;
3). 更新CallStack中指向當(dāng)前調(diào)用棧幀的L->ci,包括: ++ci、ci->base= L->base、
ci->top=L->base+nArgs、ci->func=func、ci->nresults=nresults等;
4). 解釋執(zhí)行l(wèi)ua_Fucntion函數(shù)腳本,該部分涉及Lua虛擬機(jī)具體如何實(shí)現(xiàn)解釋執(zhí)
行相關(guān)機(jī)制,此處暫不作過多說明。只需記住一點(diǎn),執(zhí)行過程中會(huì)將DataStack之前壓
入的參數(shù)逐一彈出。
(5)、步驟(4)中如果出現(xiàn)遞歸調(diào)用非C函數(shù)(lua腳本函數(shù)),則重復(fù)步驟(4);如果遞歸調(diào)用已注冊(cè)的C函數(shù),則會(huì)觸發(fā)更新L->ci值操作(類似步驟(4)的第二步子操作),然后重復(fù)進(jìn)入步驟(1)開始新的交互流程。
(6)、最后將返回值壓入DataStack中,根據(jù)返回值個(gè)數(shù)逐一將其取出,然后pop掉。
下圖表示Lua和宿主語(yǔ)言進(jìn)行多層調(diào)用時(shí)的DataStack和CallStack的狀態(tài)圖:
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注