目录
函数调用栈 - 从main方法看函数调用栈
函数调用栈 - 函数调用主流程详解
调用栈操作 - 保护方式lua_pcallk函数调用
调用栈操作 - 主函数luaD_call逻辑详解
调用栈操作 - 结果集moveresults调整
前面几章我们介绍了Lua常用的最重要的几个数据结构。这章节开始,我们开始讲解主流程篇。
主流程,一般都是从lua.c的main方法开始。那我们就从main方法开始看整个链路和流程。
从main方法中,创建完基础的lua_State *L结构后,我们就能看到Lua向数据栈上push了一个c语言的闭包方法。该方法:pmain。
看一下具体实现:
/**
* argc:行参数的个数
* argv:每个参数的值,指针结构
*/
int main (int argc, char **argv) {
int status, result;
/* 第一步:创建一个主线程栈数据结构 */
lua_State *L = luaL_newstate(); /* create state */
if (L == NULL) {
l_message(argv[0], "cannot create state: not enough memory");
return EXIT_FAILURE;
}
lua_pushcfunction(L, &pmain); /* 将pmain放入L结构上 L->top值&pmain*/
lua_pushinteger(L, argc); /* 将argc 放入L结构上 L->top值argc*/
lua_pushlightuserdata(L, argv); /* 将argv 放入L结构上 L->top值argv*/
status = lua_pcall(L, 2, 1, 0); /* 函数操作,执行pmain 函数 do the call */
result = lua_toboolean(L, -1); /* 获取pmain函数lua_pushboolean(L, 1) 的信号值get result */
report(L, status);
lua_close(L);
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}
static int pmain (lua_State *L) {
.......省.......
/* 打开常规Lua的标准库 */
luaL_openlibs(L); /* open standard libraries */
.......省.......
else dofile(L, NULL); /* executes stdin as a file */
.......省.......
lua_pushboolean(L, 1); /* 向栈顶L->top PUSH 返回值signal no errors */
return 1;
}
具体pmain函数的调用,可以看下图:
具体的整个函数调用栈操作流程如下(可以参考图片中的文件顺序进行源码阅读):
在lapi.c文件中,lua_pcallk方法是受保护的调用方式,lua_callk为非受保护的调用方式。受保护方法的唯一区别:函数的调用都不会因为错误直接导致程序直接退出,而是退回到调用点,然后将状态返回到外层的逻辑处理。
我们可以看一下ldo.c文件中的luaD_pcall方法。该方法最终调用luaD_rawrunprotected,而luaD_rawrunprotected实际最终调用f_call函数。保护性调用的情况下lua虚拟机使用lua_longjmp为函数实现堆栈续传功能,也就是当错误发生的时候,在Lua内部能够最终跳转到调用点继续向下执行。详见:《Lua源码分析 - 主流程篇 - 异常处理机制实现(09)》
/**
* 函数调用主方法(异常保护方式)
* func:f_call方法
* u:CallS 调用的方法等信息
* old_top:函数调用前的栈顶 L->top
* ef:错误状态
*/
int luaD_pcall (lua_State *L, Pfunc func, void *u,
ptrdiff_t old_top, ptrdiff_t ef) {
int status;
CallInfo *old_ci = L->ci; //老的函数回调CallInfo
lu_byte old_allowhooks = L->allowhook; //是否允许钩子
unsigned short old_nny = L->nny;
ptrdiff_t old_errfunc = L->errfunc;
L->errfunc = ef;
status = luaD_rawrunprotected(L, func, u); //异常保护调用主函数
/* 处理失败,栈状态回滚? */
if (status != LUA_OK) { /* an error occurred? */
StkId oldtop = restorestack(L, old_top);
luaF_close(L, oldtop); /* close possible pending closures */
seterrorobj(L, status, oldtop);
L->ci = old_ci;
L->allowhook = old_allowhooks;
L->nny = old_nny;
luaD_shrinkstack(L);
}
L->errfunc = old_errfunc;
return status;
}
/**
* 保护性调用(最终回调luaD_callnoyield方法)
* f=luaD_callnoyield方法
* ud=CallS *c ( c->func, c->nresults)
*/
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
unsigned short oldnCcalls = L->nCcalls;
struct lua_longjmp lj;
lj.status = LUA_OK;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
LUAI_TRY(L, &lj,
(*f)(L, ud);
);
L->errorJmp = lj.previous; /* restore old error handler */
L->nCcalls = oldnCcalls;
return lj.status;
}
/**
* 该方法包了很多层,最终调用luaD_call方法
*/
static void f_call (lua_State *L, void *ud) {
struct CallS *c = cast(struct CallS *, ud);
luaD_callnoyield(L, c->func, c->nresults);
}
/*
** Similar to 'luaD_call', but does not allow yields during the call
** 不允许挂起
*/
void luaD_callnoyield (lua_State *L, StkId func, int nResults) {
L->nny++;
luaD_call(L, func, nResults);
L->nny--;
}
无论是保护模式还是非保护模式下,最终会调用luaD_call方法。
该方法会先调用luaD_precall函数,对Lua函数执行作预处理。预处理主要作用是调整:调用栈CallInfo的堆栈
预处理函数主要逻辑:
/*
** 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.
** 真正执行一个C语言方法 or 一个Lua方法
*/
void luaD_call (lua_State *L, StkId func, int nResults) {
if (++L->nCcalls >= LUAI_MAXCCALLS)
stackerror(L);
if (!luaD_precall(L, func, nResults)) /* is a Lua function? */
luaV_execute(L); /* call it Lua方法,则执行字节码方式 */
L->nCcalls--;
}
/*
** Prepares a function call: checks the stack, creates a new CallInfo
** entry, fills in the relevant information, calls hook if needed.
** If function is a C function, does the call, too. (Otherwise, leave
** the execution ('luaV_execute') to the caller, to allow stackless
** calls.) Returns true iff function has been executed (C function).
** 预处理一个方法call
** 1. 检查栈信息
** 2. 创建一个新的CallInfo 容器
** 3. 填充相关的信息
** 4. 如果需要,回调钩子函数
*/
int luaD_precall (lua_State *L, StkId func, int nresults) {
lua_CFunction f;
CallInfo *ci;
switch (ttype(func)) {
case LUA_TCCL: /* C语言闭包 C closure */
f = clCvalue(func)->f;
goto Cfunc;
case LUA_TLCF: /* C语言函数 light C function */
f = fvalue(func);
Cfunc: {
int n; /* number of returns */
checkstackp(L, LUA_MINSTACK, func); /* ensure minimum stack size */
ci = next_ci(L); /* 创建一个新的CallInfo栈对象 now 'enter' new function */
ci->nresults = nresults; //返回的结果个数
ci->func = func; //指向需要调用的函数栈
ci->top = L->top + LUA_MINSTACK; //C语言方法最小的调用栈允许20
lua_assert(ci->top <= L->stack_last);
ci->callstatus = 0;
if (L->hookmask & LUA_MASKCALL)
luaD_hook(L, LUA_HOOKCALL, -1);
lua_unlock(L);
n = (*f)(L); /* 直接调用C语言闭包函数 do the actual call */
lua_lock(L);
api_checknelems(L, n);
luaD_poscall(L, ci, L->top - n, n); //调整堆栈
return 1; //返回1 C语言本身函数
}
case LUA_TLCL: { /* Lua方法 Lua function: prepare its call */
StkId base;
Proto *p = clLvalue(func)->p;
int n = cast_int(L->top - func) - 1; /* number of real arguments */
int fsize = p->maxstacksize; /* frame size */
checkstackp(L, fsize, func);
if (p->is_vararg)
base = adjust_varargs(L, p, n);
else { /* non vararg function */
for (; n < p->numparams; n++)
setnilvalue(L->top++); /* complete missing arguments */
base = func + 1;
}
ci = next_ci(L); /* now 'enter' new function */
ci->nresults = nresults;
ci->func = func;
ci->u.l.base = base;
L->top = ci->top = base + fsize;
lua_assert(ci->top <= L->stack_last);
ci->u.l.savedpc = p->code; /* starting point */
ci->callstatus = CIST_LUA;
if (L->hookmask & LUA_MASKCALL)
callhook(L, ci);
return 0; //返回0,Lua函数
}
default: { /* not a function */
checkstackp(L, 1, func); /* ensure space for metamethod */
tryfuncTM(L, func); /* try to get '__call' metamethod */
return luaD_precall(L, func, nresults); /* now it must be a function */
}
}
}
函数调用结束后,首先CI会回滚到上一层的调用,并调用moveresults函数,调整结果集数据。
/*
** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'.
** Handle most typical cases (zero results for commands, one result for
** expressions, multiple results for tail calls/single parameters)
** separated.
*/
static int moveresults (lua_State *L, const TValue *firstResult, StkId res,
int nres, int wanted) {
switch (wanted) { /* handle typical cases separately */
case 0: break; /* nothing to move */
case 1: { /* one result needed */
if (nres == 0) /* no results? */
firstResult = luaO_nilobject; /* adjust with nil */
setobjs2s(L, res, firstResult); /* move it to proper place */
break;
}
case LUA_MULTRET: {
int i;
for (i = 0; i < nres; i++) /* move all results to correct place */
setobjs2s(L, res + i, firstResult + i);
L->top = res + nres;
return 0; /* wanted == LUA_MULTRET */
}
default: {
int i;
if (wanted <= nres) { /* enough results? */
for (i = 0; i < wanted; i++) /* move wanted results to correct place */
setobjs2s(L, res + i, firstResult + i);
}
else { /* not enough results; use all of them plus nils */
for (i = 0; i < nres; i++) /* move all results to correct place */
setobjs2s(L, res + i, firstResult + i);
for (; i < wanted; i++) /* complete wanted number of results */
setnilvalue(res + i);
}
break;
}
}
L->top = res + wanted; /* top points after the last result */
return 1;
}