lua的类型,lua_State,函数调用,协程

数据类型

lua中的数据可以这样分为两位:值类型和引用类型。引用类型创建时需要从堆上分配内存,复制时只需要复制指针,分配的内存由GC负责维护生命期。

所有lua类型都用一个union来表示:

c 复制代码
/*
** Union of all Lua values
*/
typedef union {
  GCObject *gc;
  void *p; /* lightuserdata */
  lua_Number n;
  int b; /* boolean */
} Value;

引用类型用一个gc指针来引用,其他值类型都直接保存在Value中。

每个引用类型的开头都是CommonHeader;,所以都可强转成GCheader*使用:

c 复制代码
/*
** 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

/*
** Common header in struct form
*/
typedef struct {
  CommonHeader;
} GCheader;

/*
** Union of all collectable objects
*/
union GCObject {
  GCheader gch;
  TString ts;
  Udata u;
  Closure cl;
  Table h;
  Proto p;
  UpVal uv;
  lua_State th; /* thread */
};

为了区分Value中存放的数据类型,再额外绑定一个类型字段:

c 复制代码
/*
** Tagged Values
*/
typedef struct {
  Value value;
  int tt;
} TValue;

lua_State、数据栈、调用栈

lua_State表示一个线程/协程(后面线程与协程通用)的状态,lua_newstate用于创建主线程:

c 复制代码
/*
** Main thread combines a thread state and the global state
*/
typedef struct {
  lua_State l;
  global_State g;
} LG;

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;
  ...

用lua_newstate创建主线程的时候,同时也创建了一个global_State,所有的线程共享这个global_State,luaE_newthread用于创建协程:

c 复制代码
lua_State *luaE_newthread (lua_State *L) {
  lua_State *L1 = tostate(luaM_malloc(L, state_size(lua_State)));
  luaC_link(L, obj2gco(L1), LUA_TTHREAD);
  preinit_state(L1, G(L));
  ...

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

c 复制代码
typedef TValue *StkId; /* index to stack elements */

调用栈放在CallInfo数组中,CallInfo保存着正在调用的函数的运行状态:

c 复制代码
/*
** informations about a call
*/
typedef struct {
  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;

正在调用的函数一定存在于数据栈上,由func引用正在调用的函数对象。

base,top)指示了正在调用的函数的堆栈在数据栈上的范围。 为什么没有堆栈的当前位置?lua_State的top就是正在调用的函数的堆栈的位置啊。 ```c /* ** `per thread' 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 *last_ci; /* last free slot in the ci array*/ CallInfo *base_ci; /* array of CallInfo's */ int stacksize; int size_ci; /* size of array `base_ci' */ ... ``` \[stack,stack_last\],\[base_ci,last_ci\]分别是数据栈数组和调用栈数组,stacksize,size_ci分别是两个数组的大小,在需要的时候它们会进行增长。 ci是当前正在调用的函数的运行状态,base是该函数的栈底指针。 lua_newstate和luaE_newthread都调用了stack_init来初始化堆栈: ```c static void stack_init (lua_State *L1, lua_State *L) { /* initialize CallInfo array */ L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo); L1->ci = L1->base_ci; L1->size_ci = BASIC_CI_SIZE; L1->last_ci = L1->base_ci + L1->size_ci - 1; /* initialize stack array */ L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue); 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; } ``` 可以看到first ci只是占了个位置,它的func只是一个空值。 ### c代码中的函数调用 c代码中调用函数(包括c函数和Lua函数)使用lua_call和lua_pcall。 ```c void (lua_call) (lua_State *L, int nargs, int nresults); int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); ``` 在[使用lua时一个愚蠢的错误](https://blog.csdn.net/liuyuan185442111/article/details/138259874)中同时展示了这两个函数的用法。 二者最终都通过调用luaD_call来实现主要逻辑,但lua_pcall多了一层错误处理机制,如果函数调用过程中出错,lua_pcall会捕获错误,并调用用户通过errfunc参数传入的错误处理函数,而lua_call则会直接退出程序。这套错误处理机制是通过c的longjmp或c++的异常机制来实现的。 ```c /* ** Call a function (C or Lua). The function to be called is at *func. ** The arguments are on the stack, right after the function. ** When returns, all the results are on the stack, starting at the original ** function position. */ void luaD_call (lua_State *L, StkId func, int nResults) { if (++L->nCcalls >= LUAI_MAXCCALLS) { if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */ luaV_execute(L, 1); /* call it */ L->nCcalls--; luaC_checkGC(L); } ``` luaD_precall执行的是函数调用部分的工作,而luaD_poscall做的是函数返回的工作。对于c函数整个函数调用是连续的,luaD_precall在调用完c函数后可直接调用luaD_poscall完成工作;而lua函数执行完luaD_precall后,只是切换了lua_State的执行状态,被调用的函数的字节码尚未运行,luaD_precall返回后,调用luaV_execute去运行被调用函数的字节码,待到虚拟机执行到对应的return指令时,才会去调用luaD_poscall完成整次调用。 ### c函数的参数传递 使用lua_call或lua_pcall调用lua函数前,应先在栈上准备好函数对象和函数的参数,如果没有错误发生,函数返回时,函数对象和函数参数会出栈,函数的返回值会留在栈上。 而如果调用的是c函数时,一般是先通过lua_pushcclosure来创建c闭包,然后再调用lua_call或lua_pcall。 ```c void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { Closure *cl; lua_lock(L); luaC_checkGC(L); api_checknelems(L, n); cl = luaF_newCclosure(L, n, getcurrenv(L)); cl->c.f = fn; L->top -= n; while (n--) setobj2n(L, &cl->c.upvalue[n], L->top+n); setclvalue(L, L->top, cl); lua_assert(iswhite(obj2gco(cl))); api_incr_top(L); lua_unlock(L); } ``` 调用lua_pushcclosure前,fn需要的参数都在栈上,lua_pushcclosure会把栈上的参数转移到c闭包的上值中,然后把c闭包压入栈。在函数fn中,需要用lua_getupvalue来读取参数。 lua还提供了另一种调用c函数的方式: ```c int lua_cpcall (lua_State *L, lua_CFunction func, void *ud); ``` 在调用lua_cpcall前,不需要将参数压入栈,而是直接将参数通过ud传递给lua_cpcall。 lua_cpcall会创建一个没有上值的c闭包并压入栈中,然后将ud作为lightuserdata也压入栈,其余便和lua_pcall类似了。 在函数func中需要通过lua_touserdata取出参数。 lua_cpcall在调用luaD_call时nResults传入的是0,所以func并没有返回值。 ### 协程 协程的创建就是新建了一个lua_State,然后将lua函数对象移到新建的lua_State上: ```c int luaB_cocreate (lua_State *L) { lua_State *NL = lua_newthread(L); luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); lua_pushvalue(L, 1); /* move function to top */ lua_xmove(L, NL, 1); /* move function from L to NL */ return 1; } ``` 通过luaB_coresume启动/恢复协程,首个参数是协程对象: ```c static int luaB_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); int r; luaL_argcheck(L, co, 1, "coroutine expected"); r = auxresume(L, co, lua_gettop(L) - 1); if (r < 0) { lua_pushboolean(L, 0); lua_insert(L, -2); return 2; /* return false + error message */ } else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); return r + 1; /* return true + `resume' returns */ } } ``` 在auxresume中将coroutine.resume的其余参数从主线程转移至协程: ```c int auxresume (lua_State *L, lua_State *co, int narg) { int status = costatus(L, co); if (!lua_checkstack(co, narg)) luaL_error(L, "too many arguments to resume"); if (status != CO_SUS) { lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]); return -1; /* error flag */ } lua_xmove(L, co, narg); lua_setlevel(L, co); status = lua_resume(co, narg); ... ``` 然后去调用lua_resume,后续最终会调用resume: ```c void resume (lua_State *co, void *ud) { StkId firstArg = cast(StkId, ud); CallInfo *ci = co->ci; if (co->status == 0) { /* start coroutine? */ lua_assert(ci == co->base_ci && firstArg - co->base == 1); lua_assert(isLfunction(co->base)); if (luaD_precall(co, firstArg - 1, LUA_MULTRET) != PCRLUA) return; } else { /* resuming from previous yield */ lua_assert(co->status == LUA_YIELD); co->status = 0; if (!f_isLua(ci)) { /* `common' yield? */ /* finish interrupted execution of `OP_CALL' */ lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL || GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL); if (luaD_poscall(co, firstArg)) /* complete it... */ co->top = co->ci->top; /* and correct top if not multiple results */ } else /* yielded inside a hook: just continue its execution */ co->base = co->ci->base; } luaV_execute(co, cast_int(co->ci - co->base_ci)); } ``` 启动协程时,和调用普通lua函数一样先调用luaD_precall,再调用luaV_execute,luaV_execute中调用函数的代码如下: ```c void luaV_execute (lua_State *L, int nexeccalls) { ... case OP_CALL: { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ L->savedpc = pc; switch (luaD_precall(L, ra, nresults)) { case PCRLUA: { nexeccalls++; goto reentry; /* restart luaV_execute over new Lua function */ } case PCRC: { /* it was a C function (`precall' called it); adjust results */ if (nresults >= 0) L->top = L->ci->top; base = L->base; continue; } default: { return; /* yield */ } } } ... } int luaD_precall (lua_State *L, StkId func, int nresults) { ... else { /* if is a C function, call it */ CallInfo *ci; int n; luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ ci = inc_ci(L); /* now `enter' new function */ ci->func = restorestack(L, funcr); L->base = ci->base = ci->func + 1; ci->top = L->top + LUA_MINSTACK; lua_assert(ci->top <= L->stack_last); ci->nresults = nresults; if (L->hookmask & LUA_MASKCALL) luaD_callhook(L, LUA_HOOKCALL, -1); lua_unlock(L); n = (*ci_func(ci)->c.f)(L); /* do the actual call */ lua_lock(L); if (n < 0) /* yielding? */ return PCRYIELD; else { // n表示c函数留在栈上的结果数量,luaD_poscall会将结果调整为nresults个,放到func开始的位置 // 相当于将func和所有参数出栈,再将前nresults的返回值入栈 luaD_poscall(L, L->top - n); return PCRC; } } ... } ``` 当lua函数中碰到coroutine.yield时,经luaD_precall-\>luaB_yield,再调用到lua_yield: ```c int lua_yield (lua_State *co, int nresults) { luai_userstateyield(co, nresults); lua_lock(co); if (co->nCcalls > co->baseCcalls) luaG_runerror(co, "attempt to yield across metamethod/C-call boundary"); co->base = co->top - nresults; /* protect stack slots below */ co->status = LUA_YIELD; lua_unlock(co); return -1; } ``` lua_yield将协程置为LUA_YIELD状态,设置了co-\>base,然后一路返回到auxresume: ```c static int auxresume (lua_State *L, lua_State *co, int narg) { ... status = lua_resume(co, narg); if (status == 0 || status == LUA_YIELD) { int nres = lua_gettop(co); if (!lua_checkstack(L, nres + 1)) luaL_error(L, "too many results to resume"); lua_xmove(co, L, nres); /* move yielded values */ return nres; } else { lua_xmove(co, L, 1); /* move error message */ return -1; /* error flag */ } } ``` 因为lua_yield设置的co-\>base,lua_gettop(co)返回coroutine.yield的参数个数,将这些参数转移到主线程后,便从coroutine.resume返回了。 注意到luaD_precall中发生yield时,并没有调用luaD_poscall,当协程恢复时需要补上,至于luaD_precall中函数正常返回时,为什么没修正L-\>top,那是在lua_call中修正的。

相关推荐
码到成功>_<9 小时前
postman使用技巧
测试工具·lua·postman
King.6241 天前
SQL2API 核心理念:如何重构数据服务交付范式
大数据·开发语言·数据库·人工智能·sql·lua
巨龙之路3 天前
Lua中的元表
java·开发语言·lua
weixin_428498493 天前
在Fortran程序中嵌入Lua解释器
lua·hpc
hi星尘4 天前
Redis与Lua原子操作深度解析及案例分析
redis·lua
weixin_428498494 天前
Embedding Lua as Dynamic Configuration in C/C++
c语言·lua
程序猿John4 天前
单双线程的理解 和 lua基础语法
开发语言·lua
星空露珠4 天前
迷你世界脚本之容器接口:WorldContainer
开发语言·数据结构·数据库·游戏·lua
一个程序员(●—●)5 天前
C#调用Lua方法1+C#调用Lua方法2,3
开发语言·c#·lua
Lareina~6 天前
【元表 vs 元方法】
unity·lua