zoukankan      html  css  js  c++  java
  • lua 源码分析之线程对象lua_State

    lua_State 中放的是 lua 虚拟机中的环境表、注册表、运行堆栈、虚拟机的上下文等数据。

     从一个主线程(特指 lua 虚拟机中的线程,即 coroutine)中创建出来的新的 lua_State 会共享大部分数据,但会拥有一个独立的运行堆栈。所以一个线程对象拥有一个lua_State。

    (ps:lua 的coroutine的使用参考: http://blog.csdn.NET/wusheng520/article/details/7954666)

    lua_State共享的数据部分是全局状态机(包含GC的数据).lua_State 的运行堆栈为调用栈,lua_State 的数据栈包含当前调用栈信息。

    1、lua_state线程对象
    (1)lua_state的上下文数据
    从 C 层面看待 lua ,lua 的虚拟机对象就是一个lua_state 。 但实际上,真正的 lua虚拟机对象被隐藏起来了。那就是 lstate.h 中定义的结构体  global_State。同一 lua 虚拟机中的所有执行线程,共享了一块全局数据 global_state 。
    lua_state 是暴露给用户的数据类型,既表示一个 lua 程序的执行状态,也指代 lua 的一个线程(在官方文档中)。每个线程拥有独立的数据栈以及函数调用栈,还有独立的调试钩子和错误处理设置。所以我们不应当简单的把lua_state 看成一个静态的数据集,它是一个lua 线程的执行状态。所有的lua C API 都是围绕这个状态机:
    或把数据压入堆栈,或取出,或执行栈顶的函数,或继续上次被中断的执行过程。

    struct lua_State {
      CommonHeader;
      lu_byte status;
      StkId top;  /* first free slot in the stack */
      StkId base;  /* base of current function */
      global_State *l_G;
      CallInfo *ci;  /* call info for current function */
      const Instruction *savedpc;  /* `savedpc' of current function */
      StkId stack_last;  /* last free slot in the stack */
      StkId stack;  /* stack base */
      CallInfo *end_ci;  /* points after end of ci array*/
      CallInfo *base_ci;  /* array of CallInfo's */
      int stacksize;
      int size_ci;  /* size of array `base_ci' */
      unsigned short nCcalls;  /* number of nested C calls */
      unsigned short baseCcalls;  /* nested C calls when resuming coroutine */
      lu_byte hookmask;
      lu_byte allowhook;
      int basehookcount;
      int hookcount;
      lua_Hook hook;
      TValue l_gt;  /* table of globals */
      TValue env;  /* temporary place for environments */
      GCObject *openupval;  /* list of open upvalues in this stack */
      GCObject *gclist;
      struct lua_longjmp *errorJmp;  /* current error recover point */
      ptrdiff_t errfunc;  /* current error handling function (stack index) */
    };

    lua_State是围绕程序如何执行来设计的,数据栈和调用栈都在其中。
    其中:

    /* 
    ** Common Header for all collectable objects (in macro form, to be 
    ** included in other objects) 
    */  
    #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked  

    所有可回收的对象都有这个结构

    (2)全局状态机
    共享的一块全局数据 global_state

    /* 
    ** `global state', shared by all threads of this state 
    */  
    typedef struct global_State {  
      stringtable strt;  /* hash table for strings */  
      lua_Alloc frealloc;  /* function to reallocate memory */  
      void *ud;         /* auxiliary data to `frealloc' */  
      lu_byte currentwhite;  
      lu_byte gcstate;  /* state of garbage collector */  
      int sweepstrgc;  /* position of sweep in `strt' */  
      GCObject *rootgc;  /* list of all collectable objects */  
      GCObject **sweepgc;  /* position of sweep in `rootgc' */  
      GCObject *gray;  /* list of gray objects */  
      GCObject *grayagain;  /* list of objects to be traversed atomically */  
      GCObject *weak;  /* list of weak tables (to be cleared) */  
      GCObject *tmudata;  /* last element of list of userdata to be GC */  
      Mbuffer buff;  /* temporary buffer for string concatentation */  
      lu_mem GCthreshold;  
      lu_mem totalbytes;  /* number of bytes currently allocated */  
      lu_mem estimate;  /* an estimate of number of bytes actually in use */  
      lu_mem gcdept;  /* how much GC is `behind schedule' */  
      int gcpause;  /* size of pause between successive GCs */  
      int gcstepmul;  /* GC `granularity' */  
      lua_CFunction panic;  /* to be called in unprotected errors */  
      TValue l_registry;  
      struct lua_State *mainthread;  
      UpVal uvhead;  /* head of double-linked list of all open upvalues */  
      struct Table *mt[NUM_TAGS];  /* metatables for basic types */  
      TString *tmname[TM_N];  /* array with tag-method names */  
    } global_State;  

    从 lua 的使用者的角度看,global_state 是不可见的。我们无法用公开的 api 取到它的指针,也不需要引用它。但分析lua 的实现就不能绕开这个部分。
    global_state  里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表,有内存管理函数,有GC 需要的把所有对象串联起来的相关信息,以及一切 lua 在工作时需要的工作内存。
    通过 lua_newstate 创建一个新的 lua 虚拟机时,第一块申请的内存将用来保存主线程和这个全局状态机。lua 的实现尽可能的避免内存碎片,同时也减少内存分配和释放的次数。它采用了一个小技巧,利用一个 LG 结构,把主线程 lua_state 和 global_state 分配在一起。

    lua_newstate 这个公开 API  定义在 lstate.c中,它初始化了所有 global_state 中将引用的数据。

    LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {  
      int i;  
      lua_State *L;  
      global_State *g;  
      void *l = (*f)(ud, NULL, 0, state_size(LG));  
      if (l == NULL) return NULL;  
      L = tostate(l);  
      g = &((LG *)L)->g;  
      L->next = NULL;  
      L->tt = LUA_TTHREAD;  
      g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);  
      L->marked = luaC_white(g);  
      set2bits(L->marked, FIXEDBIT, SFIXEDBIT);  
      preinit_state(L, g);  
      g->frealloc = f;  
      g->ud = ud;  
      g->mainthread = L;  
      g->uvhead.u.l.prev = &g->uvhead;  
      g->uvhead.u.l.next = &g->uvhead;  
      g->GCthreshold = 0;  /* mark it as unfinished state */  
      g->strt.size = 0;  
      g->strt.nuse = 0;  
      g->strt.hash = NULL;  
      setnilvalue(registry(L));  
      luaZ_initbuffer(L, &g->buff);  
      g->panic = NULL;  
      g->gcstate = GCSpause;  
      g->rootgc = obj2gco(L);  
      g->sweepstrgc = 0;  
      g->sweepgc = &g->rootgc;  
      g->gray = NULL;  
      g->grayagain = NULL;  
      g->weak = NULL;  
      g->tmudata = NULL;  
      g->totalbytes = sizeof(LG);  
      g->gcpause = LUAI_GCPAUSE;  
      g->gcstepmul = LUAI_GCMUL;  
      g->gcdept = 0;  
      for (i=0; i<NUM_TAGS; i++) g->mt[i] = NULL;  
      if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) {  
        /* memory allocation error: free partial state */  
        close_state(L);  
        L = NULL;  
      }  
      else  
        luai_userstateopen(L);  
      return L;  
    } 

    2、执行状态机的数据栈和调用栈
    以下是分析 lua_state 中重要的两个数据结构:数据栈和调用栈。
    (1) 数据栈
    lua 中的数据可以这样分为两类:值类型和引用类型。值类型可以被任意复制,而引用类型共享一份数据,由 GC 负责维护生命期。lua 使用一个联合 union Value 来保存数据

    /* 
    ** Union of all Lua values 
    */  
    typedef union {  
      GCObject *gc;  
      void *p;  
      lua_Number n;  
      int b;  
    } Value;  

         从这里我们可以看到,引用类型用一个指针 GCObject *gc 来间接引用,而其它值类型都直接保存在联合中。为了区分联合中存放的数据类型,再额外绑定一个类型字段。

    #define TValuefields Value value; int tt  
      
    typedef struct lua_TValue {  
      TValuefields;  
    } TValue;  

    lua_state 的数据栈,就是一个 TValue 的数组。代码中用 StkId 类型来指代对 TValue 的引用。

    typedef TValue *StkId; /* index to stack elements */  

    在 lstate.c 中,我们可以读到对堆栈的初始化及释放的代码。

    static void stack_init (lua_State *L1, lua_State *L) {  
      /* initialize CallInfo array */  
      L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo);//lua的调用栈  
      L1->ci = L1->base_ci;  
      L1->size_ci = BASIC_CI_SIZE;  
      L1->end_ci = L1->base_ci + L1->size_ci - 1;  
      /* initialize stack array */  
      L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue);//lua的数据栈  
      L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;  
      L1->top = L1->stack;  
      L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1;  
      /* initialize first ci */  
      L1->ci->func = L1->top;//当前调用栈 的函数初始化为当前数据栈顶  
      setnilvalue(L1->top++);  /* `function' entry for this `ci' */  
      L1->base = L1->ci->base = L1->top;  
      L1->ci->top = L1->top + LUA_MINSTACK;  
    }  
    static void freestack (lua_State *L, lua_State *L1) {  
      luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo);  
      luaM_freearray(L, L1->stack, L1->stacksize, TValue);  
    }  

    一开始,数据栈的空间很有限,只有 2 倍的 LUA_MINSTACK 的大小.

    # define BASIC_STACK_SIZE (2*LUA_MINSTACK)  
    #define LUA_MINSTACK 20 

    数据栈不够用的时候,可以扩展。这种扩展是用 realloc 实现的,每次至少分配比原来大一倍的空间,并把旧的数据复制到新空间。

    void luaD_growstack (lua_State *L, int n) {  
      if (n <= L->stacksize)  /* double size is enough? */  
        luaD_reallocstack(L, 2*L->stacksize);  
      else  
        luaD_reallocstack(L, L->stacksize + n);  
    }  
    void luaD_reallocstack (lua_State *L, int newsize) {  
      TValue *oldstack = L->stack;  
      int realsize = newsize + 1 + EXTRA_STACK;  
      lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);  
      luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue);  
      L->stacksize = realsize;  
      L->stack_last = L->stack+newsize;  
      correctstack(L, oldstack);  
    }  

    数据栈扩展的过程,伴随着数据拷贝。这些数据都是可以直接值复制的,所以不需要在扩展之后修正其中的指针。 

    但,有些外部结构对数据栈的引用需要修正为正确的新地址。 这些需要修正的位置包括 upvalue以及调用栈对数据栈的引用。这个过程由 correctstack 函数实现。

    static void correctstack (lua_State *L, TValue *oldstack) {  
      CallInfo *ci;  
      GCObject *up;  
      L->top = (L->top - oldstack) + L->stack;  
      for (up = L->openupval; up != NULL; up = up->gch.next)//修正upvalue  
        gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack;  
      for (ci = L->base_ci; ci <= L->ci; ci++) {//拷贝当前调用栈(修正调用栈)  
        ci->top = (ci->top - oldstack) + L->stack;  
        ci->base = (ci->base - oldstack) + L->stack;  
        ci->func = (ci->func - oldstack) + L->stack;  
      }  
      L->base = (L->base - oldstack) + L->stack;//拷贝当前函数(修正当前函数)  
    }  

    (2)调用栈
    lua 把调用栈和数据栈分开保存。调用栈放在一个叫做 CallInfo 的结构中,以数组的形式储存在虚拟机对象(线程对象)里。

    /* 
    ** informations about a call 
    */  
    typedef struct CallInfo {  
      StkId base;  /* base for this function */  
      StkId func;  /* function index in the stack */  
      StkId top;  /* top for this function */  
      const Instruction *savedpc;  
      int nresults;  /* expected number of results from this function */  
      int tailcalls;  /* number of tail calls lost under this entry */  
    } CallInfo;  

    正在调用的函数一定存在于数据栈上,在 CallInfo 结构中,func 指向正在执行的函数在数据栈上的位置。需要记录这个信息,是因为如果当前是一个 lua 函数,且传入的参数个数不定的时候,需要用这个位置和当前数据栈底的位置相减,获得不定参数的准确数量.。(参考http://blog.csdn.net/chenjiayi_yun/article/details/8877235 lua虚拟机的体系结构)

    同时,func 还可以帮助我们调试嵌入式 lua 代码。在用 gdb这样的调试器调试代码时,可以方便的查看 C 中的调用栈信息,但一旦嵌入 lua ,我们很难理解运行过程中的 lua 代码的调用栈。不理解 lua 的内部结构,就可能面对一个简单的lua_state L 变量束手无策。

    通过访问 func 引用的函数对象来了解函数是 C 函数还是 Lua 函数。


    实际上,遍历 L 中的 base_ci域指向的 CallInfo 数组可以获得完整的 lua 调用栈。而每一级的 CallInfo 中,都可以进一步的通过 func 域取得所在函数的更详细信息。当 func 为一个 lua 函数时,根据它的函数原型可以获得源文件名、行号等诸多调试信息。

    CallInfo 是一个标准的数组结构(lua5.1.4),压栈时需要重新分配栈数组的内存。这个数组表达的是一个逻辑上的栈。

    #define inc_ci(L)   
      ((L->ci == L->end_ci) ? growCI(L) :   
       (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))  

    特别的,lua5.2以后进行了优化,调用栈修改成 双向链表,不直接被GC 模块管理,在运行过程中,并不是每次调入更深层次的函数,就立刻构造出一个CallInfo 节点。整个调用栈 CallInfo链表会在运行中被反复复用。直到 GC 的时候才清理那些比当前调用层次更深的无用节点。

  • 相关阅读:
    Light oj 1082 Array Queries(区间最小值)
    Codeforces Round #179 (Div. 2)A、B、C、D
    poj 1976 A Mini Locomotive(01背包)
    Codeforces Round #178 (Div. 2)
    hackerrank challenges median
    poj 1961 Period(kmp最短循环节)
    poj 2182 Lost Cows(树状数组)
    ZOJ1117 POJ1521 HDU1053 Huffman编码
    poj 2352 Stars 树状数组
    这可能是最适合萌新入门Web安全的路线规划
  • 原文地址:https://www.cnblogs.com/renyuan/p/6812206.html
Copyright © 2011-2022 走看看