zoukankan      html  css  js  c++  java
  • 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返回。

  • 相关阅读:
    Linux学习笔记(6)磁盘分区(LVM)
    Linux学习笔记(5)磁盘分区(parted)
    sql server升级打补丁
    sql server中index的REBUILD和REORGANIZE的区别及工作方式
    【sql server邮件】sql server如何把查询结果发邮件出去
    学习系列
    EasySQLMAIL使用实践系列
    利用EasySQLMAIL实现自动填写Excel表格并发送邮件(2)
    需求分析Point
    实现Word的列表样式
  • 原文地址:https://www.cnblogs.com/Jackie-Snow/p/5927890.html
Copyright © 2011-2022 走看看