上節說到了 lua_dofile 執行腳本文件,或者編譯過的腳本二進制文件。
這節看下,Lua 是如何區別這兩種文件的,以及虛擬機在開始執行字節碼以前,程序裏面都發生了什麼?
lua.c 裏面的調用了 lua_dofile 來執行文件,看下 lua_dofile
less
/* ** Open file, generate opcode and execute global statement. Return 0 on ** success or 1 on error. */ int lua_dofile (char *filename) { int status; int c; FILE *f = lua_openfile(filename); if (f == NULL) return 1; c = fgetc(f); ungetc(c, f); status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain(); lua_closefile(); return status; }
註釋裏寫得很清楚,這個函數是用來打開文件,生成字節碼,執行全局的語句。
成功返回 0 ,失敗返回 1 。
lua_openfile 打開文件部分已經在編譯器分析的時候說過了,這裏就再也不重複了(後面再遇到這樣的已經分析過的可能就再也不說明,直接略過了。)。
看下面的
函數
c = fgetc(f); ungetc(c, f);
這兩句是讀取文件的第一個字符,而後再把該字符放回到文件輸入流中去,使輸入流的仍是保持在文件頭的位置(也就是還沒有讀取的狀態)。
接下來的這一句
lua
status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain();
就是檢查剛纔讀到那個字符。在編譯器分析時,咱們知道編譯器生成的 *.out 二進制文件的開頭第一個字符就是 ID_CHUNK ,ASCII 碼爲 27 。而在正常的腳本文件中,這個字符是不會出現的。因此能夠根據這個標籤來斷定文件是個 *.out 的二進制文件或者是個腳本文件。
若是是編譯過的二進制文件,調用 luaI_undump 恢復場景並執行。
指針
/* ** load and run all chunks in a file */ int luaI_undump(FILE* D) { TFunc* m; while ((m=luaI_undump1(D))) { int status=luaI_dorun(m); luaI_freefunc(m); if (status!=0) return status; } return 0; }
這裏能夠看到,luaI_undump 先是調用 luaI_undump1 恢復場景,至於怎麼恢復場景的,下節再說。
恢復以後,調用 luaI_dorun 。
回到 lua_dofile,若是是一個腳本文件的話,調用 do_protectedmain。
rest
static int do_protectedmain (void) { TFunc tf; int status; jmp_buf myErrorJmp; jmp_buf *oldErr = errorJmp; errorJmp = &myErrorJmp; luaI_initTFunc(&tf); tf.fileName = lua_parsedfile; if (setjmp(myErrorJmp) == 0) { lua_parse(&tf); status = luaI_dorun(&tf); } else { status = 1; adjustC(0); /* erase extra slot */ } errorJmp = oldErr; luaI_free(tf.code); return status; }
這裏咱們看到,在作一些初始化,和設置異常恢復斷點以後,語法分析 lua_parse 以後,它也是調用了 luaI_dorun ,調到這一步的時候,編譯過二進制文件和腳本文件就沒有差異了。setjmp 能夠先簡單的認爲是 C 語言版的 try...catch 異常處理, 雖然它們之間是有區別的。
後面代碼是作一些異常發生的善後處理,設置狀態位,釋放相關的資源。
code
int luaI_dorun (TFunc *tf) { int status; adjustC(1); /* one slot for the pseudo-function */ stack[CBase].tag = LUA_T_FUNCTION; stack[CBase].value.tf = tf; status = do_protectedrun(0); adjustC(0); return status; }
把編譯好的 TFunc 設置到棧上,調用 do_protectedrun。
資源
/* ** Execute a protected call. Assumes that function is at CBase and ** parameters are on top of it. Leave nResults on the stack. */ static int do_protectedrun (int nResults) { jmp_buf myErrorJmp; int status; StkId oldCBase = CBase; jmp_buf *oldErr = errorJmp; errorJmp = &myErrorJmp; if (setjmp(myErrorJmp) == 0) { do_call(CBase+1, nResults); CnResults = (top-stack) - CBase; /* number of results */ CBase += CnResults; /* incorporate results on the stack */ status = 0; } else { /* an error occurred: restore CBase and top */ CBase = oldCBase; top = stack+CBase; status = 1; } errorJmp = oldErr; return status; }
註釋比較清楚,再也不細說。函數名字中有 protected 字樣的就是受保護的調用,內部使用 setjmp 來設置異常恢復點。函數調用 do_call 執行剛纔壓棧的 TFunc.
get
/* ** Call a function (C or Lua). The parameters must be on the stack, ** between [stack+base,top). The function to be called is at stack+base-1. ** When returns, the results are on the stack, between [stack+base-1,top). ** The number of results is nResults, unless nResults=MULT_RET. */ static void do_call (StkId base, int nResults) { StkId firstResult; Object *func = stack+base-1; int i; if (tag(func) == LUA_T_CFUNCTION) { tag(func) = LUA_T_CMARK; firstResult = callC(fvalue(func), base); } else if (tag(func) == LUA_T_FUNCTION) { tag(func) = LUA_T_MARK; firstResult = lua_execute(func->value.tf->code, base); } else { /* func is not a function */ /* Call the fallback for invalid functions */ open_stack((top-stack)-(base-1)); stack[base-1] = luaI_fallBacks[FB_FUNCTION].function; do_call(base, nResults); return; } /* adjust the number of results */ if (nResults != MULT_RET && top - (stack+firstResult) != nResults) adjust_top(firstResult+nResults); /* move results to base-1 (to erase parameters and function) */ base--; nResults = top - (stack+firstResult); /* actual number of results */ for (i=0; i<nResults; i++) *(stack+base+i) = *(stack+firstResult+i); top -= firstResult-base; }
判斷棧上的是 C 函數仍是,Lua 的函數。
若是是 C 的調用 callC。
若是是 Lua 的函數,則調用 lua_execute 由虛擬機執行。
若是不是一個函數,則調用 "function" 的回退函數。
以後,是調整返回值和棧。
調整棧有一個簡單的規則就是:函數調用後棧的狀態要和函數調用前保持同樣,除了可能在棧上剩下幾個返回值。這句話可能不容易理解,舉個小例子。好比在 C 語言裏,咱們有一個函數:
編譯器
int add(int a, int b) { return a + b; }
若是咱們有一個調用 int result = add(3, 5),在這句執行完後,它所產生的效果和 int result = 8 是徹底同樣的。套到這裏就是,在該調用 add(3, 5) 的地方,你直接放個 8 到棧上去效果是同樣的,對於後續程序的執行徹底沒有任何影響。
回憶一下彙編裏的函數調用返回的棧楨結構,是否是以爲這裏的函數調用返回有點眼熟了。
callC 就是調用一個 C 的函數指針。
lua_execute 是一個很大的 switch...case 來執行指令。這個在以前的版本里有詳細介紹,在這個版本里可能就略過了。
fallback 對程序的主流程基本沒什麼影響。
----------------------------------------
到目前爲止的問題:
> luaI_undump1 怎麼恢復場景的
----------------------------------------
虛擬機