首页 > 代码库 > skynet源码阅读<1>

skynet源码阅读<1>

    阅读skynet的lua-c交互部分代码时,可以看到如下处理:

struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));

    那么,问题来了:skynet_context是如何作为upvalue与C函数绑定在一起的呢?这里以luaopen_skynet_core(lua_State *L)为例:

int luaopen_skynet_core(lua_State *L) {	luaL_checkversion(L);	luaL_Reg l[] = { /*注册函数部分略去*/ { NULL, NULL } };	luaL_newlibtable(L, l);	lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");	struct skynet_context *ctx = lua_touserdata(L,-1);	if (ctx == NULL) {		return luaL_error(L, "Init skynet context first");	}	luaL_setfuncs(L,l,1);	return 1;}

  这里先通过luaL_newlibtable创建一张表T(函数指针表l并未实际注册到表T中,只是分配了相应大小的空间),然后从全局索引表中取出事先注册的skynet_context,接着调用luaL_setfuncs(L,l,1),将函数表注册到T中。注册时每个函数都会从栈顶取出指定数目的元素(这里为1)作为upvalue。到lua源码中看看这部分具体的实现如何:

/*** set functions from list ‘l‘ into table at top - ‘nup‘; each** function gets the ‘nup‘ elements at the top as upvalues.** Returns with only the table at the stack.*/LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {  luaL_checkstack(L, nup, "too many upvalues");  for (; l->name != NULL; l++) {  /* fill the table with given functions */    int i;    for (i = 0; i < nup; i++)  /* copy upvalues to the top */      lua_pushvalue(L, -nup);    lua_pushcclosure(L, l->func, nup);  /* closure with those upvalues */    lua_setfield(L, -(nup + 2), l->name); /*-(nup+2)位置的元素正是之前创建的表T;这里以函数entry的name为key,向其注册闭包*/  }  lua_pop(L, nup);  /* remove upvalues */}

    可以看到扫描每个函数entry时,都会将push到栈顶的upvalues全部复制新的一份出来到栈顶,接着通过lua_pushcclosure创建C闭包,并注册到表T中去。函数表注册完毕后,再清理掉栈顶的upvalues。此时栈顶就是完成注册的表T了。至此,每个C闭包已经关联了skynet-context作为它们的upvalue。至于skynet_context,则是加载lua服务时,会创建C服务snlua(负责加载lua虚拟机),此时会将context实例注册到lua虚拟机的全局注册表中。细节见service_snlua.c,这里不再赘述。

    先看下C闭包的定义(lobject.h):

typedef struct CClosure {  ClosureHeader;  lua_CFunction f;  TValue upvalue[1];  /* list of upvalues */} CClosure;

    除了ClosureHeader之外(包括upvalue的个数,CommonHeader的对象类型tt_,标记mark_等),可以看到C闭包包含一个函数指针f和一个upvalue数组。upvalue数组的实际大小是根据upvalue的个数来创建的。这里要做的事情就是把upvalue拷进C闭包中,如下(lapi.c):

LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {  lua_lock(L);  if (n == 0) {    setfvalue(L->top, fn);  }  else {    CClosure *cl;    api_checknelems(L, n);    api_check(L, n <= MAXUPVAL, "upvalue index too large");    cl = luaF_newCclosure(L, n);    cl->f = fn;    L->top -= n;    while (n--) {      setobj2n(L, &cl->upvalue[n], L->top + n);      /* does not need barrier because closure is white */    }    setclCvalue(L, L->top, cl);  }  api_incr_top(L);  luaC_checkGC(L);  lua_unlock(L);}

    这里将栈上的元素copy到cl的upvalue数组中,然后再将cl设置到栈顶。之前被这里的while循环绕了一下,设n为N0,减一之后为N1,那么n--的运算结果为N0,但是循环中n的值则是N1,二者并不一样,逻辑是正确的。

    那么剩下的最后一个问题是,在C函数中,是如何取到相关的upvalue的?首先是通过lua_upvalueindex(1)拿到对应的upvalue的索引:

#define lua_upvalueindex(i)	(LUA_REGISTRYINDEX - (i))

  在本例中,进入lua_touserdata,可以看到index2addr的实现:

static TValue *index2addr (lua_State *L, int idx) {  CallInfo *ci = L->ci;  if (idx > 0) {    TValue *o = ci->func + idx;    api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index");    if (o >= L->top) return NONVALIDVALUE;    else return o;  }  else if (!ispseudo(idx)) {  /* negative index */    api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index");    return L->top + idx;  }  else if (idx == LUA_REGISTRYINDEX)    return &G(L)->l_registry;  else {  /* upvalues */    idx = LUA_REGISTRYINDEX - idx;    api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large");    if (ttislcf(ci->func))  /* light C function? */      return NONVALIDVALUE;  /* it has no upvalues */    else {      CClosure *func = clCvalue(ci->func);      return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE;    }  }}

    在upvalue的处理这块,先是还原upvalue在C闭包中的index,然后从当前的CallInfo中取到闭包,拿到upvalue返回。

skynet源码阅读<1>