本文主要是介绍skynet源码阅读<1>--lua与c的基本交互,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
阅读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返回。
转载于:https://www.cnblogs.com/Jackie-Snow/p/5927890.html
这篇关于skynet源码阅读<1>--lua与c的基本交互的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!