在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
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线程对象 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 所有可回收的对象都有这个结构 /* ** `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 的实现就不能绕开这个部分。 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、执行状态机的数据栈和调用栈 /* ** 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)调用栈 /* ** 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 函数。
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 的时候才清理那些比当前调用层次更深的无用节点。 |
请发表评论