lua虛擬機概述


何為虛擬機

用於模擬計算機運行的程序.是個中間層,它處於腳本語言和硬件之間的一個程序.每一門腳本語言都會有自己定義的opcode(”操作碼”),可以理解為這門程序自己定義的”匯編語言”.一般的編譯型語言,比如C等,經過編譯器編譯之后生成的都是與當前硬件環境相匹配的匯編代碼;而腳本型的語言,經過前端的處理之后,生成的就是opcode,再將該opcode放在這門語言的虛擬機中執行.虛擬機是作為單獨的程序獨立存在,而Lua由於是一門嵌入式的語言是附着在宿主環境中的.

lua代碼到虛擬機執行的流程

在Lua中,Lua代碼從詞法分析到語法分析再到生成opcode,最后進入虛擬機執行的大體流程是什么樣子的呢?

Lua的API中提供了luaL_dofile函數,它實際上是個宏,內部首先調用luaL_loadfile函數,加載Lua代碼進行語法,詞法分析,生成Lua虛擬機可執行的代碼,再調用lua_pcall函數,執行其中的代碼:

    #define luaL_dofile(L, fn) \
    	(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

前半部分調用luaL_loadfile函數對Lua代碼進行詞法和語法分析,后半部分調用lua_pcall將第一步中分析的結果(也就是opcode)到虛擬機中執行.

首先來看luaL_loadfile函數,暫時不深入其中研究它如何分析一個Lua代碼文件,先看它最后輸出了什么.它最終會調用f_parser函數,這是對一個Lua代碼進行分析的入口函數:

    static void f_parser (lua_State *L, void *ud) {
      int i;
      Proto *tf;
      Closure *cl;
      struct SParser *p = cast(struct SParser *, ud);
      int c = luaZ_lookahead(p->z);
      luaC_checkGC(L);
      tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
                                                                 &p->buff, p->name);
      cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
      cl->l.p = tf;
      for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */
        cl->l.upvals[i] = luaF_newupval(L);
      setclvalue(L, L->top, cl);
      incr_top(L);
    }

在完成詞法分析之后,返回了Proto類型的指針tf,然后將其綁定在新創建的Closure指針上,初始化UpValue,最后壓入Lua棧中.

不難想像,Lua詞法分析之后產生的opcode等相關數據都在這個Proto類型的結構體中.

再來看lua_pcall函數是如何將產生的opcode放入虛擬機執行的.

lua_pcall函數中,首先獲取需要調用的函數指針:

    c.func = L->top - (nargs+1);  /* function to be called */

這里的nargs是由函數參數傳入的,luaL_dofile中調用lua_pcall時這里傳入的參數是0,換句話說,這里得到的函數對象指針就是在f_parser函數中最后放入Lua棧的指針.

繼續往下執行,走到luaD_call函數,有這一段代碼:


      if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
        luaV_execute(L, 1);  /* call it */

進入luaV_execute函數,這里是虛擬機執行代碼的主函數:


    void luaV_execute (lua_State *L, int nexeccalls) {
      LClosure *cl;
      StkId base;
      TValue *k;
      const Instruction *pc;
     reentry:  /* entry point */
      lua_assert(isLua(L->ci));
      pc = L->savedpc;
      cl = &clvalue(L->ci->func)->l;
      base = L->base;
      k = cl->p->k;
      /* main loop of interpreter */
      for (;;) {
        const Instruction i = *pc++;
        StkId ra;
        if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) &&
            (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
          traceexec(L, pc);
          if (L->status == LUA_YIELD) {  /* did hook yield? */
            L->savedpc = pc - 1;
            return;
          }
          base = L->base;
        }
        /* warning!! several calls may realloc the stack and invalidate `ra' */
        ra = RA(i);
    // 以下是各種opcode的情況處理
    }

可以看到,這里的pc指針里存放的是虛擬機opcode代碼,它最開始從L->savepc初始化而來,而L->savepc在luaD_precall中賦值:

    L->savedpc = p->code;  /* starting point */

這里的p就是第一步f_parser中返回的Proto指針.

回顧一下整個流程:

  1. 函數f_parser中,對Lua代碼文件的分析返回了Proto指針

  2. 函數luaD_precall中,將Lua_state的savepc指針指向1中的Proto結構體的code指針

  3. 函數luaV_execute中,pc指針指向2中的savepc指針,緊跟着就是一個大的循環體,依次取出其中的opcode進行執行.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM