zoukankan      html  css  js  c++  java
  • skynet中动态库的处理

      skynet中的.so动态库由service-src中的c文件编译完后生成,其中最重要的是snlua.c.

      源码地址:https://github.com/cloudwu/skynet/service-src

      这里不介绍如何生成动态库,而是介绍当编译成动态库后,skynet是如何利用里边的函数的.

      源码:

      #include "skynet.h"

      #include <lua.h>
      #include <lualib.h>
      #include <lauxlib.h>

      #include <assert.h>
      #include <string.h>
      #include <stdlib.h>
      #include <stdio.h>

      struct snlua {
        lua_State * L;
        struct skynet_context * ctx;
      };

      // LUA_CACHELIB may defined in patched lua for shared proto
      #ifdef LUA_CACHELIB

      #define codecache luaopen_cache

      #else

      static int
        cleardummy(lua_State *L) {
        return 0;
      }

      static int
      codecache(lua_State *L) { //该函数由_init中luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0)
      luaL_Reg l[] = {
        { "clear", cleardummy },
        { "mode", cleardummy },
        { NULL, NULL },
      };
      luaL_newlib(L,l); //向lua中注册“clear”和“mode”函数
      lua_getglobal(L, "loadfile");//把全局变量 name 里的值压栈,返回该值的类型,目前尚不知道loadfile是在哪里注册到lua中的?
      lua_setfield(L, -2, "loadfile");//做一个等价于 t[k] = v 的操作, 这里 t 是给出的索引处的值, 而 v 是栈顶的那个值。
      return 1;
      }  

      #endif

      static int
      traceback (lua_State *L) {
        const char *msg = lua_tostring(L, 1);
        if (msg)
        luaL_traceback(L, L, msg, 1);
        else {
        lua_pushliteral(L, "(no error message)");
        }
        return 1;
      }

      static void
      _report_launcher_error(struct skynet_context *ctx) {
        // sizeof "ERROR" == 5
        skynet_sendname(ctx, 0, ".launcher", PTYPE_TEXT, 0, "ERROR", 5);
      }

      static const char *
      optstring(struct skynet_context *ctx, const char *key, const char * str) {
        const char * ret = skynet_command(ctx, "GETENV", key);
        if (ret == NULL) {
        return str;
      }
      return ret;
      }

      static int
      _init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
        lua_State *L = l->L;
        l->ctx = ctx;
        lua_gc(L, LUA_GCSTOP, 0); //LUA_GCSTOP: 停止垃圾收集器
        lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ //放个nil值到栈上
        lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); //LUA_REGISTRYINDEX?,"LUA_NOENV"?
        luaL_openlibs(L);
        lua_pushlightuserdata(L, ctx); //lua中的lightuserdata存放的是c语言中的变量,这里为结构体指针
          lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
          luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0);
        //如果 modname 不在 package.loaded 中, 则调用函数 openf ,并传入字符串 modname。 将其返回值置入 package.loaded[modname]。 这个行            为好似该函数通过 require 调用过一样。

        //如果glb 为真, 同时也讲模块设到全局变量 modname 里。

        //在栈上留下该模块的副本。
        lua_pop(L,1);

        const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");
        lua_pushstring(L, path);
        lua_setglobal(L, "LUA_PATH");
        const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");
        lua_pushstring(L, cpath);
        lua_setglobal(L, "LUA_CPATH");
        const char *service = optstring(ctx, "luaservice", "./service/?.lua");
        lua_pushstring(L, service);
        lua_setglobal(L, "LUA_SERVICE");
        const char *preload = skynet_command(ctx, "GETENV", "preload");
        lua_pushstring(L, preload);
        lua_setglobal(L, "LUA_PRELOAD"); //

        lua_pushcfunction(L, traceback); //以上将各种path注册到lua的全局变量中,变量名字即为 "LUA_xxxx"
        assert(lua_gettop(L) == 1);

        const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");

        int r = luaL_loadfile(L,loader); //加载loader.lua
        if (r != LUA_OK) {
        skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));
        _report_launcher_error(ctx);
        return 1;
      }
      lua_pushlstring(L, args, sz); //将bootstart传入
      r = lua_pcall(L,1,0,1); //load bootstart 服务
      if (r != LUA_OK) {
        skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
        _report_launcher_error(ctx);
        return 1;
      }
      lua_settop(L,0);

      lua_gc(L, LUA_GCRESTART, 0);

      return 0;
      }

      static int
      _launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) { //ud就是服务中的结构体
        assert(type == 0 && session == 0); //确保_launch服务在一个进程中只调用一次
        struct snlua *l = ud;
        skynet_callback(context, NULL, NULL);
        int err = _init(l, context, msg, sz);
        if (err) {
          skynet_command(context, "EXIT", NULL);
        }

        return 0;
      }

      int
      snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) { //BOOTSTART
        int sz = strlen(args);
        char * tmp = skynet_malloc(sz);
        memcpy(tmp, args, sz);
        skynet_callback(ctx, l , _launch); //注册回掉函数为_launch,当工作线程轮询到的时候调用的回掉函数即为"_launch"
        const char * self = skynet_command(ctx, "REG", NULL); //服务有handle和名字,这里为服务注册名字,若名字为NULL,则返回handle的字符串     “:handle”
        uint32_t handle_id = strtoul(self+1, NULL, 16);
        // it must be first message
        skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz); //给他自己发送一个信息,目的地为刚注册的地址,最终将该消息push到对应的mq上
        return 0;
      }

      struct snlua *
      snlua_create(void) {
        struct snlua * l = skynet_malloc(sizeof(*l));
        memset(l,0,sizeof(*l));
        l->L = lua_newstate(skynet_lalloc, NULL);
        return l;
      }

      void
      snlua_release(struct snlua *l) {
        lua_close(l->L);
        skynet_free(l);
      }

      void
      snlua_signal(struct snlua *l, int signal) {
        skynet_error(l->ctx, "recv a signal %d", signal);
        #ifdef lua_checksig
        // If our lua support signal (modified lua version by skynet), trigger it.
        skynet_sig_L = l->L;
        #endif
      }

      以上为service_snlua.c中的内容,初看起来一头雾水,我们来看一下整个过程。

      skynet.main.c中的main函数最后调用skynet_start.c中的skynet_start函数,其中启动snlua服务代码部分如下:

      bootstrap(ctx, config->bootstrap); //创建snlua服务模块,及ctx

      这句话是启动snlua模块,具体bootstrap代码:

      static void
      bootstrap(struct skynet_context * logger, const char * cmdline) {
        int sz = strlen(cmdline);
        char name[sz+1];
        char args[sz+1];
        sscanf(cmdline, "%s %s", name, args);   //最终name = "snlua" , args = "bootstrap" 
        struct skynet_context *ctx = skynet_context_new(name, args);  //启动snlua , bootstarp为参数
        if (ctx == NULL) {
        skynet_error(NULL, "Bootstrap error : %s ", cmdline);
        skynet_context_dispatchall(logger);
        exit(1);
        }
      }

      函数参数 : logger,忽略 

           cmdline : " snlua bootstrap”

      让我们看一下skynet_contex_new( "snlua"  , "bootstrap" )都做了些什么,该函数在skynet_server.c中

      

      struct skynet_module {
        const char * name;
        void * module; 动态链接库的指针.so
        skynet_dl_create create; 
        skynet_dl_init init;
        skynet_dl_release release;
        skynet_dl_signal signal; //skynet.module.c
      }; //结构体中的create init release signal 分别对应着service_snlua.c中的create init release signal的函数地址,skynet_dl_XX是宏定义

      struct skynet_context * 

      skynet_context_new(const char * name, const char *param) { //name为服务名,param为需要的参数

        struct skynet_module * mod = skynet_module_query(name); //根据name即"snlua"来得到对应名字的mod地址,有就直接查到,没有先打开动态库,获取里边的函数地址,并注册该服务,并返回
        //module里实际上是改动态库中的内容
        if (mod == NULL) //正常情况下mod不会为NULL
        return NULL;

        void *inst = skynet_module_instance_create(mod); //调用mod中的create函数,实际上就是service_snlua.c中的create,即调用dlopen来获得动态库
        if (inst == NULL)
        return NULL;
        struct skynet_context * ctx = skynet_malloc(sizeof(*ctx)); //为每个服务创建一个ctx
        CHECKCALLING_INIT(ctx)

        ctx->mod = mod; //里边有动态库指针和动态库中的函数指针及该库的名字,根据名字已经注册到了skynet
        ctx->instance = inst; //每个服务都有一个结构体,如logger的指针
        ctx->ref = 2; //why
        ctx->cb = NULL;
        ctx->cb_ud = NULL;
        ctx->session_id = 0;
        ctx->logfile = NULL;

        ctx->init = false;
        ctx->endless = false;
        // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
        ctx->handle = 0;
        ctx->handle = skynet_handle_register(ctx); //将该ctx注册到handle中
        struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
        // init function maybe use ctx->handle, so it must init at last
        context_inc();

        CHECKCALLING_BEGIN(ctx)
        int r = skynet_module_instance_init(mod, inst, ctx, param); //执行mod中的init函数,若为0执行成功
        CHECKCALLING_END(ctx)
        if (r == 0) {
          struct skynet_context * ret = skynet_context_release(ctx); //减少ctx的引用计数,若为0,删除ctx资源
          if (ret) {
            ctx->init = true;
          }
        skynet_globalmq_push(queue); //将创建的服务对应的队列push到全局队列
        if (ret) {
          skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
        }
        return ret;
        } else {
          skynet_error(ctx, "FAILED launch %s", name);
          uint32_t handle = ctx->handle;  
          skynet_context_release(ctx);
          skynet_handle_retire(handle);
          struct drop_t d = { handle };
          skynet_mq_release(queue, drop_message, &d);
          return NULL;
        }
      }

      接下来我们看一下skynet_module.c中的关于动态库看起

      应该从skynet_module_query(const char * name)看起 , 此处name为“snlua”

      

      struct skynet_module * 
      skynet_module_query(const char * name) {
        struct skynet_module * result = _query(name);//首先查找snlua module是否存在
        if (result)
          return result; //存在则返回

        SPIN_LOCK(M)

        result = _query(name); // double check

        if (result == NULL && M->count < MAX_MODULE_TYPE) { //不存在,接下来就是重点
          int index = M->count; //M中记录共有多少个c module创建了,->count 为下一个module的索引
          void * dl = _try_open(M,name); //调用dlopen来打开动态库,返回动态库指针
          if (dl) {
            M->m[index].name = name;
            M->m[index].module = dl;

            if (_open_sym(&M->m[index]) == 0) { //调用_open_sym来获取动态库中的函数地址,具体看下面实现
              M->m[index].name = skynet_strdup(name);
              M->count ++;
              result = &M->m[index];
            }
          }
        }

        SPIN_UNLOCK(M)

        return result;
      }

      struct modules {
        int count;
        struct spinlock lock;
        const char * path;
        struct skynet_module m[MAX_MODULE_TYPE];
      };

      static struct modules * M = NULL;

      static void *
      _try_open(struct modules *m, const char * name) { M , “snlua”
        const char *l;
        const char * path = m->path; //path就是初始化M时传入的动态库所在的路径
        size_t path_size = strlen(path);
        size_t name_size = strlen(name); //“snlua”

        int sz = path_size + name_size;
        //search path
        void * dl = NULL;
        char tmp[sz];
        do
        {
          memset(tmp,0,sz);
          while (*path == ';') path++;
          if (*path == '') break;
          l = strchr(path, ';');
          if (l == NULL) l = path + strlen(path);
          int len = l - path;
          int i;
          for (i=0;path[i]!='?' && i < len ;i++) {
          tmp[i] = path[i];
        }
        memcpy(tmp+i,name,name_size);
        if (path[i] == '?') {
          strncpy(tmp+i+name_size,path+i+1,len - i - 1);
        } else {
          fprintf(stderr,"Invalid C service path ");
          exit(1);
        } //以上循环为拼凑出完成的动态库路径,用真是动态库名字替代 “?”
        dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL); //打开动态库,返回动态库指针
        path = l;
        }while(dl == NULL);

        if (dl == NULL) {
        fprintf(stderr, "try open %s failed : %s ",name,dlerror());
      }

      return dl;
      }

      static struct skynet_module *
      _query(const char * name) {
        int i;
        for (i=0;i<M->count;i++) {
          if (strcmp(M->m[i].name,name)==0) {
            return &M->m[i];
          }
        }
        return NULL;
      }

      static int
      _open_sym(struct skynet_module *mod) {
        size_t name_size = strlen(mod->name);
        char tmp[name_size + 9]; // create/init/release/signal , longest name is release (7)
        memcpy(tmp, mod->name, name_size);
        strcpy(tmp+name_size, "_create");
        mod->create = dlsym(mod->module, tmp);//tmp为snlua_create
        strcpy(tmp+name_size, "_init");
        mod->init = dlsym(mod->module, tmp);//snlua_init
        strcpy(tmp+name_size, "_release");//snlua_release
        mod->release = dlsym(mod->module, tmp);
        strcpy(tmp+name_size, "_signal");
        mod->signal = dlsym(mod->module, tmp); //snlua_signal

        return mod->init == NULL;
      }

      总结:snlua相当于lua服务的入口,在snlua的 _init函数中根据参数名称如(bootstrap)等启动对应的lua服务。

         即首先到module中查找该c服务(例如snlua)是否存在,若不存在则调用动态库函数打开该库,获取该库的必要函数。

         然后为该服务创建队列,并放到全局消息队列中。

         

      skyney中每个动态库,都是扩展的服务,有自己的消息队列,回掉函数

      下面介绍下标题内容即动态库操作的函数,这里用到: 

      

      #include <dlfcn.h>
    
      void *dlopen(const char *filename, int flag);
    
      char *dlerror(void);
    
      void *dlsym(void *handle, const char *symbol);
    
      int dlclose(void *handle);
      具体使用参考:http://www.cnblogs.com/Anker/p/3746802.html
      
      未完,待续。

      

      

      

      

  • 相关阅读:
    shi_tomasi特征点,GFTTDetector
    特征点总结(按features2d.hpp源码由上至下总结)
    经过一年时间的沉淀 再次回首 TCP Socket服务器编程 (二)
    构建嵌入式小型Linux系统
    老赵书托(3):深入理解计算机系统
    #define与typedef区别
    Linux下快速静态编译Qt以及Qt动态/静态版本共存
    tcpdump示例
    linux绑定多个ip(转载)
    Linux下eclipse及mysql安装,c++访问mysql数据库
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/5327515.html
Copyright © 2011-2022 走看看