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只会被一个线程持有调度,也就是消息处理是单线程的。


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

原文地址:https://www.cnblogs.com/watercoldyi/p/5804743.html