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
      
      未完,待续。

      

      

      

      

  • 相关阅读:
    Balance的数学思想构造辅助函数
    1663. Smallest String With A Given Numeric Value (M)
    1680. Concatenation of Consecutive Binary Numbers (M)
    1631. Path With Minimum Effort (M)
    1437. Check If All 1's Are at Least Length K Places Away (E)
    1329. Sort the Matrix Diagonally (M)
    1657. Determine if Two Strings Are Close (M)
    1673. Find the Most Competitive Subsequence (M)
    1641. Count Sorted Vowel Strings (M)
    1679. Max Number of K-Sum Pairs (M)
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/5327515.html
Copyright © 2011-2022 走看看