歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Lua執行字節碼的過程介紹

Lua執行字節碼的過程介紹

日期:2017/3/1 9:29:33   编辑:Linux編程

前面一篇文章中介紹了lua給下面代碼生成最終的字節碼的整個過程,這次我們來看看lua vm執行這些字節碼的過程。

1 foo = "bar"
2 local a, b = "a", "b"
3 foo = a

生成的字節碼如下所示:

之前lua是在luaY_parser函數(入口)中完成了lua腳本的解析生成字節碼的整個過程的,在生成了main func(過程見“Lua解析賦值類型代碼的過程“)後luaY_parser會返回一個Proto結構體指針tf,Proto結構將描述整個main func的所有信息。

 1 //如果此字符是LUA_SIGNATURE中的第一個字符說明文件內容是預編譯好的文件內容,因此利用函數luaU_undump來加載一個預編譯後的代碼塊
 2   //否則是未編譯的腳本源碼,利用luaY_parser來對源碼進行parse
 3   tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
 4                                                              &p->buff, p->name);
 5   cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
 6   cl->l.p = tf;
 7   for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */
 8     cl->l.upvals[i] = luaF_newupval(L);
 9   setclvalue(L, L->top, cl);
10   incr_top(L);

接下來第5行,函數luaF_newLclosure生成了一個Closure結構體來表示lua的closure,然後下一行將Proto結構體地址傳給cl保存,接下來的循環裡cl的upvalue數組記錄下main func中的upvalue,然後setclvalue函數將cl放入到lua stack的棧頂上,incr_top將棧頂L->top加一。此時lua stack的頂部存放了包裹了main func的closure結構體,下面lua將會調用lua_pcall函數來執行這個closure了,也即vm加載整個生成的字節碼並加以解釋。

 1 LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) {
 2   struct CallS c;
 3   //... ...
 4   c.func = L->top - (nargs+1);  /* function to be called */
 5   c.nresults = nresults;
 6   status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
 7   //... ...
 8 }
 9 /*
10 ** Execute a protected call.
11 */
12 struct CallS {  /* data to `f_call' */
13   StkId func;
14   int nresults;
15 };

首先第4行根據要執行的函數參數數量和L->top的值來算出function在lua stack中的位置並將其保存到CallS結構體中,其中CallS結構體中的StkId類型為stack下標類型。接著第6行將c和f_call函數一起傳入luaD_pcall函數中,luaD_pcall函數執行一些標志的設置後調用函數luaD_rawrunprotected,函數luaD_rawrunprotected內部調用f_call並將c作為其參數傳入。如下所示:

1 static void f_call (lua_State *L, void *ud) {
2   struct CallS *c = cast(struct CallS *, ud);
3   luaD_call(L, c->func, c->nresults);
4 }

在luaD_call中首先判斷lua此時是否到達了函數調用層次的最大值,超過這報錯否則判斷要執行的函數是不是lua function,是的話就調用luaV_execute函數來運行vm執行字節碼。

 1 void luaD_call (lua_State *L, StkId func, int nResults) {
 2   if (++L->nCcalls >= LUAI_MAXCCALLS) {
 3     if (L->nCcalls == LUAI_MAXCCALLS)
 4       luaG_runerror(L, "C stack overflow");
 5     else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
 6       luaD_throw(L, LUA_ERRERR);  /* error while handing stack error */
 7   }
 8   if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
 9     luaV_execute(L, 1);  /* call it */
10   L->nCcalls--;
11   luaC_checkGC(L);
12 }

luaV_execute函數是vm執行字節碼的核心過程,整個函數約有400行代碼,由於整個過程分支太多,我們只講解示例中的字節碼解析過程。

 1 void luaV_execute (lua_State *L, int nexeccalls) {
 2   LClosure *cl;
 3   StkId base;
 4   TValue *k;
 5   const Instruction *pc;
 6  reentry:  /* entry point */
 7   lua_assert(isLua(L->ci));
 8   pc = L->savedpc;
 9   cl = &clvalue(L->ci->func)->l;
10   base = L->base;
11   k = cl->p->k;
12 //... ...

L->savedpc為字節碼數組的指針,因此pc保存了當前要執行字節碼的下標,clvalue萃取出當前要執行的lua function對應的closure,k指向了當前function的常量數組。

下面先來看看vm解釋loadk01字節碼的過程。

 1 /* main loop of interpreter */
 2   for (;;) {
 3     const Instruction i = *pc++;
 4     StkId ra;
 5     //... ...
 6     ra = RA(i);
 7     //... ...
 8     switch (GET_OPCODE(i)) {
 9       //... ...
10       case OP_LOADK: {
11         setobj2s(L, ra, KBx(i));
12         continue;
13       }
14 //... ...

第3行i保存了當前要執行的字節碼,同時pc指向下一條字節碼,第6行ra保存了通過宏RA萃取出的字節碼中的a部分並與function stack的base相加得出的stack中的值;第8行Get_OPCODE宏萃取出字節碼i的類型,結果是OP_LOADK,因此調用了setobj2s函數,其中KBx宏萃取出字節碼i的bx部分並與function的常量數組地址相加得出的常量值,這裡ra指向了function stack中相應的位置,KBx(i)部分指向了當前function中常量數組中存放的常量“bar”。

1 /* from stack to (same) stack */
2 #define setobjs2s    setobj
3 /* to stack (not from same stack) */
4 #define setobj2s    setobj
5 
6 #define setobj(L,obj1,obj2) \
7   { const TValue *o2=(obj2); TValue *o1=(obj1); \
8     o1->value = o2->value; o1->tt=o2->tt; \
9     checkliveness(G(L),o1); }

obj1為ra,obj2為KBx結果。可以看到第7行將這兩個值轉換為了TValue,並將o2的value設為o1的value,o2的值的類型設為o1的類型,效果上完成了將“bar”的值存放在了function stack上。接著又返回到上面的主循環處讀取下一個字節碼並執行,下一個要執行的字節碼為setglobal00.

 1 switch (GET_OPCODE(i)) {
 2       //... ...
 3       case OP_SETGLOBAL: {
 4         TValue g;
 5         sethvalue(L, &g, cl->env);
 6         lua_assert(ttisstring(KBx(i)));
 7         Protect(luaV_settable(L, &g, KBx(i), ra));
 8         continue;
 9       }
10 //... ...

首先第5行中,cl->env為當前function的環境,函數sethvalue將其傳給了g,KBx(i)指向了function常量數組中的值,ra為stack中的值,這裡為前一條字節碼loadk保存在stack中的“bar”。

 1 void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
 2   int loop;
 3   TValue temp;
 4   for (loop = 0; loop < MAXTAGLOOP; loop++) {
 5     const TValue *tm;
 6     if (ttistable(t)) {  /* `t' is a table? */
 7       Table *h = hvalue(t);
 8       TValue *oldval = luaH_set(L, h, key); /* do a primitive set */
 9       if (!ttisnil(oldval) ||  /* result is no nil? */
10           (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
11         setobj2t(L, oldval, val);
12         h->flags = 0;
13         luaC_barriert(L, h, val);
14         return;
15       }
16  //... ...
17 }

這裡首先判斷g是不是table,然後第7行取得g的hash部分,通過第8行luaH_set裡的luaH_get得到table中key對應的old value。最後第11行,函數setobj2t將val("bar")存放在了全局變量foo的位置處,即foo = “bar”。

1 #define setobj(L,obj1,obj2) \
2   { const TValue *o2=(obj2); TValue *o1=(obj1); \
3     o1->value = o2->value; o1->tt=o2->tt; \
4     checkliveness(G(L),o1); }

好了到了這裡語句foo = “bar”對應的兩條字節碼的解釋過程已經全部介紹完了,下面的三條字節碼就不再詳細解釋了,大家可以按照上面的路線自己過一遍~

Lua 語言 15 分鐘快速入門 http://www.linuxidc.com/Linux/2013-06/86582.htm

Lua程序設計(第2版)中文 PDF http://www.linuxidc.com/Linux/2013-03/81833.htm

Lua程序設計(第二版)閱讀筆記 http://www.linuxidc.com/Linux/2013-03/81834.htm

NetBSD 將支持用 Lua 腳本開發內核組件 http://www.linuxidc.com/Linux/2013-02/79527.htm

CentOS 編譯安裝 Lua LuaSocket http://www.linuxidc.com/Linux/2011-08/41105.htm

Lua 的詳細介紹:請點這裡
Lua 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved