標籤:io os 使用 ar for sp 檔案 on 問題
上節說到了 lua_dofile 執行指令檔,或者編譯過的指令碼二進位檔案。
這節看下,Lua 是如何區別這兩種檔案的,以及虛擬機器在開始執行位元組碼之前,程式裡面都發生了什嗎?
lua.c 裡面的調用了 lua_dofile 來執行檔案,看下 lua_dofile
/*** 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);
這兩句是讀取檔案的第一個字元,然後再把該字元放回到檔案輸入資料流中去,使輸入資料流的還是保持在檔案頭的位置(也就是尚未讀取的狀態)。
接下來的這一句
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。
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 異常處理, 雖然它們之間是有區別的。
後面代碼是做一些異常發生的善後處理,設定狀態位,釋放相關的資源。
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.
/*** 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 怎麼恢複情境的
----------------------------------------
Lua2.4 執行之前 opcode.c