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 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 == '