1 数据结构和宏
1.1 协程的状态码
(lua.h)
#define LUA_YIELD 1 //挂起
#define LUA_ERRRUN 2 //运行时错误
#define LUA_ERRSYNTAX 3 //语法错误
#define LUA_ERRMEM 4 //内存错误
#define LUA_ERRERR 5 //在错误处理函数中出错
1.2 协程的执行状态码
(lbaselib.c)
#define CO_RUN 0 //执行中状态
#define CO_SUS 1 //挂起状态
#define CO_NOR 2 //常规状态(这个协程唤醒了另一个协程)
#define CO_DEAD 3 //死亡状态
//协程执行状态码对应的字符串
static const char *const statnames[] =
{"running", "suspended", "normal", "dead"};
1.3 lua_State 协程结构体
(lstate.h) lua_State
struct lua_State
{
CommonHeader;//#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
lu_byte status;//协程的状态码
StkId top;//栈顶位置,“寄存器”第一个可用位置
StkId base;//当前函数调用的栈基址
global_State* l_G;//全局状态机
CallInfo* ci;//当前函数调用信息
const Instruction* savedpc;//指令指针
StkId stack_last;//“寄存器”最后一个可用位置
StkId stack;//栈数组的起始位置
CallInfo* end_ci;//函数调用信息数组的最后一个位置的下一个位置
CallInfo* base_ci;//函数调用信息数组首地址
int stacksize;//栈的大小
int size_ci;//函数调用信息数组大小
unsigned short nCcalls;//内嵌C调用的层数
unsigned short baseCcalls;//唤醒协程时的内嵌C调用层数
lu_byte hookmask;
lu_byte allowhook;
int basehookcount;
int hookcount;
lua_Hook hook;
TValue l_gt;//Global表
TValue env;//环境表的临时位置
GCObject* openupval;//栈上open状态的uvalues
GCObject* gclist;
struct lua_longjmp* errorJmp;//当前跳转信息,实现try catch的关键结构
ptrdiff_t errfunc;//当前错误处理函数相对于“寄存器数组首地址”的偏移地址
};
2 C API
2.1 lua_newthread 新建一个协程,压栈,返回这个新协程
(lapi.c) lua_newthread
LUA_API lua_State* lua_newthread(lua_State* L)
{
luaC_checkGC(L);
lua_State* L1 = luaE_newthread(L);
setthvalue(L, L->top, L1);
api_incr_top(L);//#define api_incr_top(L) { L->top++; }
return L1;
}
(lstate.c) luaE_newthread 新建一个协程并返回这个协程
lua_State* luaE_newthread(lua_State* L)
{
//#define tostate(l) (cast(lua_State *, cast(lu_byte *, l) + LUAI_EXTRASPACE))
//#define state_size(x) (sizeof(x) + LUAI_EXTRASPACE)
lua_State* L1 = tostate(luaM_malloc(L, state_size(lua_State)));
luaC_link(L, obj2gco(L1), LUA_TTHREAD);
//初始化协程
preinit_state(L1, G(L));
//栈初始化(初始化 CallInfo 数组,“寄存器” 数组,第一个CallInfo)
stack_init(L1, L);
//所有的协程的Global表都是用的主协程的Global表哦
setobj2n(L, gt(L1), gt(L));
L1->hookmask = L->hookmask;
L1->basehookcount = L->basehookcount;
L1->hook = L->hook;
resethookcount(L1);//#define resethookcount(L) (L->hookcount = L->basehookcount)
return L1;
}
2.2 luaE_freethread 释放一个协程L1
(lstate.c)
void luaE_freethread(lua_State* L, lua_State* L1)
{
//关闭协程L1上所有upvalue
luaF_close(L1, L1->stack);
//释放协程L1上“寄存器”数组和CallInfo数组
freestack(L, L1);
//释放协程L1本身的内存
//#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0)
//#define fromstate(l) (cast(lu_byte *, (l)) - LUAI_EXTRASPACE)
//#define state_size(x) (sizeof(x) + LUAI_EXTRASPACE)
luaM_freemem(L, fromstate(L1), state_size(lua_State));
}
2.3 lua_status 获取协程的错误码
(lapi.c) lua_status
LUA_API int lua_status(lua_State* L)
{
return L->status;
}
2.4 lua_resume 唤醒一个协程,nargs为参数个数,返回值为协程状态码
(ldo.c) lua_resume
LUA_API int lua_resume(lua_State* L, int nargs)
{
if (L->status != LUA_YEILD && (L->status != 0 || L->ci != L->base_ci))
{
return resume_error(L, "cannot resume non-suspend coroutine");
}
if (L->nCcalls >= LUAI_MAXCCALLS)
{
return resume_error(L, "C stack overflow");
}
L->baseCcalls = ++L->nCcalls;
int status = luaD_rawrunprotected(L, resume, L->top - nargs);
if (status != 0)
{
//记录状态码
L->status = cast_byte(status);
//根据状态码status将错误入栈
luaD_seterrorobj(L, status, L->top);
L->ci->top = L->top;
}
else
{
status = L->status;
}
--L->nCcalls;
return status;
}
(ldo.c) resume_error 将错误信息msg压栈
static int resume_error(lua_State* L, const char* msg)
{
L->top = L->ci->top;
setsvalue2s(L, L->top, luaS_new(L, msg));
incr_top(L);
return LUA_ERRRUN;
}
(ldo.c) resume 唤醒协程
static void resume(lua_State* L, void* ud)
{
StkId firstArg = cast(StkId, ud);
CallInfo* ci = L->ci;
//若状态码为0,则执行函数
if (L->status == 0)
{
//若是C函数,直接结束,因为若是C函数那么在luaD_precall里面就执行过了
if (luaD_precall(L, fisrtArg - 1, LUA_MULTRET) != PCRLUA)
{
return;
}
}
else
{
L->status = 0;
//若是C函数
//#define ci_func(ci) (clvalue((ci)->func))
//#define f_isLua(ci) (!ci_func(ci)->c.isC)
if (!f_isLua(ci))
{
//函数收尾工作。若函数实际返回值数量!=函数原型返回值数量,则调整栈顶
if (luaD_poscall(L, firstArg))
{
L->top = L->ci->top;
}
}
//若是lua函数,恢复函数调用栈基址
else
{
L->base = L->ci->base;
}
}
//继续执行指令
luaV_execute(L, cast_int(L->ci - L->base_ci));
}
2.5 lua_yield 挂起一个协程
(ldo.c) lua_yield
LUA_API int lua_yield(lua_State* L, int nresults)
{
if (L->nCcalls > L->baseCcalls)
{
luaG_runerror(L, "attempt to yield across metamethod/C-call boundary");
}
//调整函数调用的栈基址
L->base = L->top - nresults;
//标记协程状态为LUA_YIELD
L->status = LUA_YIELD;
return -1;
}
3 Lua API
3.1 协程模块的注册
3.1.1 luaL_register(L, LUA_COLIBNAME, co_funcs) 注册协程模块
(lbaselib.c) luaopen_base
LUALIB_API int luaopen_base(lua_State* L)
{
base_open(L);//base_open见模块章节
//注册协程模块,LUA_COLIBNAME为模块名,co_funcs为库函数列表
//#define LUA_COLIBNAME "coroutine"
luaL_register(L, LUA_COLIBNAME, co_funcs);//luaL_register见模块章节
return 2;
}
3.1.2 co_funcs 注册的协程库函数列表
(lbaselib.c)
static const luaL_Reg co_funcs[] = {
{"create", luaB_cocreate},
{"resume", luaB_coresume},
{"running", luaB_corunning},
{"status", luaB_costatus},
{"wrap", luaB_cowrap},
{"yield", luaB_yield},
{NULL, NULL}
};
3.2 luaB_cocreate 创建一个协程,协程留在栈顶
(lbaselib.c) luaB_cocreate
static int luaB_cocreate(lua_State* L)
{
//新建一个协程NL
lua_State* NL = lua_newthread(L);
//将函数移到栈顶(前提:函数已经就是第一个参数了)
lua_pushvalue(L, 1);
//将函数从协程L移到协程NL
lua_xmove(L, NL, 1);
return 1;
}
3.3 luaB_costatus 获取当前协程的状态,将状态字符串压栈
(lbaselib.c) luaB_costatus
static int luaB_costatus(lua_State* L)
{
lua_State* co = lua_tothread(L, 1);
lua_pushstring(L, statnames[costatus(L, co)]);
return 1;
}
(lbaselib.c) costatus 获取协程的状态码
static int costatus(lua_State* L, lua_State* co)
{
//若L==co,必定是 执行中状态
if (L == co)
{
return CO_RUN;
}
//根据co的状态码,来决定其运行状态
switch (lua_status(co))
{
//挂起状态
case LUA_YIELD:
{
return CO_SUS;
}
//若为0,看情况
case 0:
{
lua_Debug ar;
//若还有栈帧,则说明是normal状态
if (lua_getstack(co, 0, &ar) > 0)
{
return CO_NOR;
}
//否则,若无参数,则说明协程已死
else if (lua_gettop(co) == 0)
{
return CO_DEAD;
}
//其余情况,则为挂起状态
else
{
return CO_SUS;
}
}
//其余情况则为死亡状态
default:
{
return CO_DEAD;
}
}
}
(ldebug.c) lua_getstack 获取第level层函数调用的栈的状态
LUA_API int lua_getstack(lua_Stack* L, int level, lua_Debug* ar)
{
//寻找 满足 level==0 或者 为base_ci 的 CallInfo
for (CallInfo* ci = L->ci; level > 0 && ci > L->base_ci; ci--)
{
level--;
//#define ci_func(ci) (clvalue((ci)->func))
//#define f_isLua(ci) (!ci_func(ci)->c.isC)
if (f_isLua(ci))
{
level -= ci->tailcalls;
}
}
if (level == 0 && ci > L->base_ci)
{
status = 1;
ar->i_ci = cast_int(ci -> L->base_ci);
}
else if (level < 0)
{
status = 1;
ar->i_ci = 0;
}
//ci == L->base_ci
else
{
status = 0;
}
return status;
}
3.4 luaB_coresume 唤醒一个协程,栈顶为[true值,协程yield或return的值] 或者为[false值,,错误信息]
(lbaselib.c) luaB_coresume
static int luaB_coresume(lua_State* L)
{
//协程为第一个参数,传给协程的参数个数为lua_gettop(L) - 1
lua_State* co = lua_tothread(L, 1);
//唤醒协程,r若<0则表示出错,r>=0表示 协程yield或return的值的数量
int r = auxresume(L, co, lua_gettop(L) - 1);
if (r < 0)
{
//错误信息在栈顶,false值插入错误信息下
lua_pushboolean(L, 0);
lua_insert(L, -2);
return 2;
}
else
{
//true值插入r个返回值之下
lua_pushboolean(L, 1);
lua_insert(L, -(r + 1));
return r + 1;
}
}
(lbaselib.c) auxresume 恢复协程co,参数narg个,返回值为 协程yield或return的值的数量
static 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");
}
//co不是挂起状态,则报错
if (status != CO_SUS)
{
lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
return -1;
}
//将协程L栈顶的narg个值出栈,移到协程co的栈顶
lua_xmove(L, co, narg);
//将协程L的C调用层数 赋值给 协程co的C调用层数
lua_setlevel(L, co);//co->nCcalls = L->nCcalls;
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");
}
//将协程co yeild的返回值压入 协程L的栈顶
lua_xmove(co, L, nres);
return nres;
}
else
{
//将栈顶的错误信息从协程co移到协程L
lua_xmove(co, L, 1);
return -1;
}
}
3.5 luaB_corunning 获取当前正在执行的协程,压栈
(lbaselib.c) luaB_corunning
static int luaB_corunning(lua_State* L)
{
//将L放在L的栈顶
if (lua_pushthread(L))//若 G(L)->mainthread == L 为true
{
lua_pushnil(L);//若为主协程,则返回nil。因为lua用的不对称协程,所以主协程不能当作一般的协程看待。
}
return 1;
}
3.6 luaB_cowrap 创建一个协程和一个C闭包,并以该协程作为该C闭包的upvalue,将该C闭包压栈
(lbaselib.c) luaB_cowrap
static int luaB_cowrap(lua_State* L)
{
luaB_cocreate(L);
lua_pushcclosure(L, luaB_auxwrap, 1);
return 1;
}
(lbaselib.c) luaB_auxwrap 协程的包装函数
static int luaB_auxwrap(lua_State* L)
{
lua_State* co = lua_tothread(L, lua_upvalueindex(1));
//唤醒协程
int r = auxresume(L, co, lua_gettop(L));
if (r < 0)
{
//若栈顶是字符串类型,则栈顶的为错误信息
if (lua_isstring(L, -1))
{
luaL_where(L, 1);//获取堆栈信息
lua_insert(L, -2);//插入错误信息之前
lua_concat(L, 2);//连接错误信息
}
lua_error(L);//传递错误信息
}
return r;
}
3.7 luaB_yield 挂起协程
(lbaselib.c) luaB_yield
static int luaB_yield(lua_State* L)
{
return lua_yield(L, lua_gettop(L));
}