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\n", 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 == '\0') 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\n");
      exit(1);
    } //以上循环为拼凑出完成的动态库路径,用真是动态库名字替代 “?”
    dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL); //打开动态库,返回动态库指针
    path = l;
    }while(dl == NULL);

    if (dl == NULL) {
    fprintf(stderr, "try open %s failed : %s\n",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
  
  未完,待续。

  

  

  

  

skynet中动态库的处理的更多相关文章

  1. Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名

    Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf  动态库的后缀为*.so  静态库的后缀为 libxxx.a   ldconfig   目录名 转载自:http://b ...

  2. ios 开发中 动态库 与静态库的区别

    使用静态库的好处 1,模块化,分工合作 2,避免少量改动经常导致大量的重复编译连接 3,也可以重用,注意不是共享使用 动态库使用有如下好处: 1使用动态库,可以将最终可执行文件体积缩小 2使用动态库, ...

  3. Linux系统中“动态库”和“静态库”那点事儿【转】

    转自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻. ...

  4. gcc中动态库和静态库的链接顺序

    so文件:动态库a文件: 静态库exe文件:可执行程序(linux下以文件属性来标示是否是可执行文件,与后缀名无关) 经过自己写的一些测试程序,大致了解了下gcc中链接顺序问题,总结出以下几点:1,动 ...

  5. Linux系统中“动态库”和“静态库”那点事儿

    摘自http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在 ...

  6. 024-linux中动态库libXXX.so

    1.动态库的概念.动态链接库与普通的程序相比而言,没有main函数,是一系列函数的实现.通过shared和fPIC编译参数生产so动态链接库文件.程序在调用库函数时,只需要连接上这个库即可. 2.动态 ...

  7. Linux中的静态库与动态库

    什么是库文件? 库文件是事先编译好的方法的合集.比如:我们提前写好一些数据公式的实现,将其打包成库文件,以后使用只需要库文件就可以,不需要重新编写. Linux系统中: 1.静态库的扩展名为.a:2. ...

  8. Android 5.0 到 Android 6.0 + 的深坑之一 之 .so 动态库的适配

    (原创:http://www.cnblogs.com/linguanh) 目录: 前序 一,问题描述 二,为何会如此"无情"? 三,目前存在该问题的知名SDK 四,解决方案,1 对 ...

  9. iOS 静态库和动态库的区别&静态库的生成

    linux中静态库和动态库的区别 一.不同 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函 ...

随机推荐

  1. python发送邮件(yagmail模块)

    import yagmail user = 'xxxx@qq.com' passwd = 'xxxx' # 授权码,不是密码,需要在邮箱中设置,看邮箱类型,有的需要设置 res = yagmail.S ...

  2. WPF中DPI的问题

    先搞清楚一下几个概念: DPI:dots  per  inch ,每英寸的点数.我们常说的鼠标DPI,是指鼠标移动一英寸的距离滑过的点数:打印DPI,每英寸的长度打印的点数:扫描DPI,每英寸扫描了多 ...

  3. POJ 1125 Stockbroker Grapevine【floyd简单应用】

    链接: http://poj.org/problem?id=1125 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=22010#probl ...

  4. UTF-8, UTF-16, UTF-32 & BOM

    FAQ - UTF-8, UTF-16, UTF-32 & BOM http://www.unicode.org/faq/utf_bom.html General questions, rel ...

  5. 客户端-服务器通信安全 sign key

    API接口签名校验,如何安全保存appsecret? - 知乎  https://www.zhihu.com/question/40855191 要保证一般的客户端-服务器通信安全,可以使用3个密钥. ...

  6. 博文分类索引--Python

    Python 基础 [python]-- 初识python [python]-- 基本语法.循环 [python]-- 列表 [python]-- 元组.字典 [python]-- 字符串.字符编码与 ...

  7. 数据库之MySQL(一)

    概述 1.什么是数据库 ?   数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQL Server等 ...

  8. 理解java注解

    @是java注解,即annotation. 注解功能可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件. Java注解是附加在代码中的一些元信息,用于一些工具在编译 ...

  9. JDBC注册驱动程序的三种方式

    1. Class.forName("com.mysql.jdbc.Driver");//加载数据库驱动 Class.forName("com.mysql.jdbc.Dri ...

  10. Binary Search in Java

    关于折半查找中的几个注意点. Version 1: public static <T extends Comparable<? super T>> int binSearch( ...