zoukankan      html  css  js  c++  java
  • skynet源码分析2:模块

    actor在skynet中称为模块,每个模块由皮囊和骨骼组成。皮囊承载用户逻辑,骨骼承载内部框架逻辑。

    皮囊(skynet_module)

    皮囊在框架中用skynet_module对象表示,实现在skynet-src/skynet_module.c中,代表一个动态库.下文用sm来称呼.

    先来看看sm的定义,在skynet-src/skynet.h中

     1 typedef void * (*skynet_dl_create)(void);
     2 typedef int (*skynet_dl_init)(void * inst, struct skynet_context *, const char * parm);
     3 typedef void (*skynet_dl_release)(void * inst);
     4 typedef void (*skynet_dl_signal)(void * inst, int signal);
     5 
     6 struct skynet_module {
     7     const char * name;
     8     void * module;
     9     skynet_dl_create create;
    10     skynet_dl_init init;
    11     skynet_dl_release release;
    12     skynet_dl_signal signal;
    13 };

     name:动态库名

    module:动态库的句柄

    create:契约函数之一,用来创建用户对象。

    init:契约函数之一,用来初始化用户对象。inst为用户对象,skynet_context为模块骨骼对象(在皮囊层,不需要知道它是什么,只是调用框架方法时需要用到它,后续或分析它),parm为创建actor时带的参数,等同于main函数中的args参数。

    release:契约函数之一,用来释放用户对象。

    signal:契约函数之一,用来实现信号功能。signal为信号类型.

     

    skynet_module.c这个部分实现了一个动态库加载器,有两个作用:

    1. 加载动态库,获取契约函数指针。
    2. 缓存已加载的动态库。

    我们来看看它的实现吧,它头文件的接口定义如下:

    1 void skynet_module_insert(struct skynet_module *mod);
    2 struct skynet_module * skynet_module_query(const char * name);
    3 void * skynet_module_instance_create(struct skynet_module *);
    4 int skynet_module_instance_init(struct skynet_module *, void * inst, struct skynet_context *ctx, const char * parm);
    5 void skynet_module_instance_release(struct skynet_module *, void *inst);
    6 void skynet_module_instance_signal(struct skynet_module *, void *inst, int signal);
    7 
    8 void skynet_module_init(const char *path);

    在linux下,动态库相关接口定义在<dlfcn.h>中,主要有dlopen,dlsym,dlclose,dlerror函数,dlopen用来加载so库,得到句柄;dlsym用来获取符号的地址;dlclose用来卸载so库;dlerror获取前面几个函数调用失败的错误信息。这几个接口用起来都非常简单。

    那么它的具体实现必然是使用dlfcn的几个接口。

    下面主要来看看加载逻辑,也就是skynet_module_query函数,3-6的函数是对契约函数的代理调用。

    在skynet_module.c中,可以看到如下内部定义:

     1 #define MAX_MODULE_TYPE 32
     2 
     3 struct modules {
     4     int count;
     5     struct spinlock lock;
     6     const char * path;
     7     struct skynet_module m[MAX_MODULE_TYPE];
     8 };
     9 
    10 static struct modules * M = NULL;

    m是用于缓存sm,最大32个,path表示动态库的搜索路径,与lua的package的语义一致,后面再说。

    来看skynet_module_query,在skynet-src/skynet_module.c的93行:

     1 struct skynet_module * 
     2 skynet_module_query(const char * name) {
     3     struct skynet_module * result = _query(name);
     4     if (result)
     5         return result;
     6 
     7     SPIN_LOCK(M)
     8 
     9     result = _query(name); // double check
    10 
    11     if (result == NULL && M->count < MAX_MODULE_TYPE) {
    12         int index = M->count;
    13         void * dl = _try_open(M,name);
    14         if (dl) {
    15             M->m[index].name = name;
    16             M->m[index].module = dl;
    17 
    18             if (_open_sym(&M->m[index]) == 0) {
    19                 M->m[index].name = skynet_strdup(name);
    20                 M->count ++;
    21                 result = &M->m[index];
    22             }
    23         }
    24     }
    25 
    26     SPIN_UNLOCK(M)
    27 
    28     return result;
    29 }

    逻辑比较简单,先从缓存查找,未找到就加载,然后添加至缓存。

    3-5行是查找缓存,_query为一个数组搜索函数。用自旋锁保证线程安全,9又查找了一次缓存,因为可能在锁竞争的时候,其它线程加载了一个同样的so。

    后面这个判断我不觉历,只能加载35个不同的so,也就是说框架不能有35个不同逻辑的actor。

    _try_open来看一下,在24行:

     1 static void *
     2 _try_open(struct modules *m, const char * name) {
     3     const char *l;
     4     const char * path = m->path;
     5     size_t path_size = strlen(path);
     6     size_t name_size = strlen(name);
     7 
     8     int sz = path_size + name_size;
     9     //search path
    10     void * dl = NULL;
    11     char tmp[sz];
    12     do
    13     {
    14         memset(tmp,0,sz);
    15         while (*path == ';') path++;
    16         if (*path == '') break;
    17         l = strchr(path, ';');
    18         if (l == NULL) l = path + strlen(path);
    19         int len = l - path;
    20         int i;
    21         for (i=0;path[i]!='?' && i < len ;i++) {
    22             tmp[i] = path[i];
    23         }
    24         memcpy(tmp+i,name,name_size);
    25         if (path[i] == '?') {
    26             strncpy(tmp+i+name_size,path+i+1,len - i - 1);
    27         } else {
    28             fprintf(stderr,"Invalid C service path
    ");
    29             exit(1);
    30         }
    31         dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL);
    32         path = l;
    33     }while(dl == NULL);
    34 
    35     if (dl == NULL) {
    36         fprintf(stderr, "try open %s failed : %s
    ",name,dlerror());
    37     }
    38 
    39     return dl;
    40 }

    主要是对path这个搜索路径的解析,path可以定义一组路径模板,每个路径用';'隔开,如:“./service/?.so;./http/?.so”,假如模块名为foo,那么就会被解析成"./service/foo.so;./http/foo.so",然后用这两个路径依次调用dlopen,直到有成功加载的。

    _open_sym是调用dlsym获取四个契约函数的指针,从实现可以得知,契约函数的命名规则为:模块名_函数名.

    最后将sm存入m.


    骨骼(skynet_context) 


    下文用sc代指。

    sc主要承载框架的调度逻辑。定义在skynet-src/skynet_server.c中:

    struct skynet_context {
        void * instance;
        struct skynet_module * mod;
        void * cb_ud;
        skynet_cb cb;
        struct message_queue *queue;
        FILE * logfile;
        char result[32];
        uint32_t handle;
        int session_id;
        int ref;
        bool init;
        bool endless;
    
        CHECKCALLING_DECL
    };

    mod:皮囊对象.

    instance:用契约函数create创建的。

    cb:处理消息的回调函数,由皮囊逻辑里注册。

    cb_ud:回调函数的用户数据。

    queue:actor的信箱,存放收到的消息。

    handle:标识自己的句柄,用于生命周期的管理。

    logfile:文件句柄,用与录像功能(将所有收到的消息记录与文件).

    result:handle的16进制字符,便于传递。

    session_id:上一次分配的session,用于分配不重复的session。

    ref:引用计数。

    init:是否初始化。

    endless:是否在处理消息时死循环。

    这里只分析sc的创建,先来看创建函数skynet_context_new,定义在skynet-src/skynet_server.c的119行:

     1 struct skynet_context * 
     2 skynet_context_new(const char * name, const char *param) {
     3     struct skynet_module * mod = skynet_module_query(name);
     4 
     5     if (mod == NULL)
     6         return NULL;
     7 
     8     void *inst = skynet_module_instance_create(mod);
     9     if (inst == NULL)
    10         return NULL;
    11     struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
    12     CHECKCALLING_INIT(ctx)
    13 
    14     ctx->mod = mod;
    15     ctx->instance = inst;
    16     ctx->ref = 2;
    17     ctx->cb = NULL;
    18     ctx->cb_ud = NULL;
    19     ctx->session_id = 0;
    20     ctx->logfile = NULL;
    21 
    22     ctx->init = false;
    23     ctx->endless = false;
    24     // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
    25     ctx->handle = 0;    
    26     ctx->handle = skynet_handle_register(ctx);
    27     struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
    28     // init function maybe use ctx->handle, so it must init at last
    29     context_inc();
    30 
    31     CHECKCALLING_BEGIN(ctx)
    32     int r = skynet_module_instance_init(mod, inst, ctx, param);
    33     CHECKCALLING_END(ctx)
    34     if (r == 0) {
    35         struct skynet_context * ret = skynet_context_release(ctx);
    36         if (ret) {
    37             ctx->init = true;
    38         }
    39         skynet_globalmq_push(queue);
    40         if (ret) {
    41             skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
    42         }
    43         return ret;
    44     } else {
    45         skynet_error(ctx, "FAILED launch %s", name);
    46         uint32_t handle = ctx->handle;
    47         skynet_context_release(ctx);
    48         skynet_handle_retire(handle);
    49         struct drop_t d = { handle };
    50         skynet_mq_release(queue, drop_message, &d);
    51         return NULL;
    52     }
    53 }

    1、加载sm对象,调用create取得用户对象.

    2、分配sc,注册handle,分配信箱.

    3、调用init初始化用户对象.

    之所以到处有一些CALLINGCHECK宏,主要是为了检测调度是否正确,因为skynet调度时,每个actor只会被一个线程持有调度,也就是消息处理是单线程的。


    未完待续,不当之处请道友指正。

  • 相关阅读:
    在使用SMO时,检索IsSystemObject属性将导致性能急剧下降的解决方法
    在没有安装.NET Framework 3.5的机器上使用LINQ等特性
    我的网站中的组织层次结构图信息与AD的同步问题
    有关视图状态(ViewState)的一些问题
    窗体控件visible属性问题
    SiteMapNode的几个特殊属性
    创建本地数据库时发生错误及其解决方案
    在基于Windows验证的Web应用程序中检索当前用户的所有角色
    在数据库恢复之前将数据库置为单用户模式
    将ASP.NET网站嵌入到MOSS站点下面的做法
  • 原文地址:https://www.cnblogs.com/watercoldyi/p/5804743.html
Copyright © 2011-2022 走看看