1.基本概念:模塊與服務(wù)
模塊(module):在skynet中,模塊是指符合規(guī)范的 C 共享庫(kù)文件。一個(gè)符合規(guī)范的 C 共享庫(kù)應(yīng)當(dāng)具備 *_create、*_signal、*_release 以及 *_init 四個(gè)接口。其中 * 代表模塊名稱(chēng)。其中模塊的接口及定義如下:
//skynet_module.h
//每一個(gè)模塊都應(yīng)當(dāng)提供 create、init、release 以及 signal 等四個(gè)接口
typedef void * (*skynet_dl_create)(void);
typedef int (*skynet_dl_init)(void * inst, struct skynet_context *, const char * parm);
typedef void (*skynet_dl_release)(void * inst);
typedef void (*skynet_dl_signal)(void * inst, int signal);
struct skynet_module {
const char * name; //模塊名稱(chēng)
void * module; //用于訪問(wèn)對(duì)應(yīng)so庫(kù)的句柄,由dlopen函數(shù)獲得
skynet_dl_create create;
skynet_dl_init init;
skynet_dl_release release;
skynet_dl_signal signal;
};
//skynet_module.c
#define MAX_MODULE_TYPE 32
//modules 列表,用于存放全部用到的 module
struct modules {
int count; //存放的 module 的數(shù)量
struct spinlock lock;
const char * path; //path由配置文件中的module_path提供
struct skynet_module m[MAX_MODULE_TYPE]; //存儲(chǔ)module的數(shù)組
};
static struct modules * M = NULL;
服務(wù)(service):相對(duì)于模塊是靜態(tài)的概念,服務(wù)則是動(dòng)態(tài)的概念,指的是運(yùn)行在獨(dú)立上下文中的模塊。
skynet 提供了這樣的一種機(jī)制:用戶(hù)可以將自定義的模塊放置到 skynet 指定的目錄下。當(dāng) skynet 使用到對(duì)應(yīng)的服務(wù)時(shí),會(huì)將該模塊加載到 modules 當(dāng)中,并為其創(chuàng)建一個(gè)獨(dú)立的上下文環(huán)境(context)。這樣不同的服務(wù)的運(yùn)行環(huán)境相互透明,交互則通過(guò)消息隊(duì)列來(lái)進(jìn)行。
//skynet_server.c:
struct skynet_context {
void * instance; //調(diào)用模塊的 *_create 函數(shù)創(chuàng)建對(duì)應(yīng)的服務(wù)實(shí)例
struct skynet_module * mod; //指向?qū)?yīng)的模塊
void * cb_ud; //回調(diào)函數(shù)所需參數(shù)
skynet_cb cb; //回調(diào)函數(shù)
struct message_queue *queue; //服務(wù)所屬的消息隊(duì)列
FILE * logfile; //日志文件句柄
uint64_t cpu_cost; // in microsec
uint64_t cpu_start; // in microsec
char result[32]; //存放回調(diào)函數(shù)的執(zhí)行結(jié)果
uint32_t handle; //位于該上下文環(huán)境中的一個(gè)服務(wù)的句柄
int session_id; //session_id 用來(lái)將請(qǐng)求和響應(yīng)匹配起來(lái)
int ref; //引用計(jì)數(shù),當(dāng) ref == 0 時(shí)回收內(nèi)存
int message_count; //消息隊(duì)列中消息的數(shù)量?
bool init; //是否完成了初始化
bool endless; //該服務(wù)是否是一個(gè)無(wú)限循環(huán)
bool profile;
CHECKCALLING_DECL
};
2.模塊的加載
在 skynet 中,模塊的加載主要通過(guò) skynet_module_query 函數(shù)來(lái)完成。當(dāng) skynet 啟動(dòng)時(shí)會(huì)先執(zhí)行 skynet_module_init 函數(shù)對(duì)全局模塊列表 modules 進(jìn)行初始化。當(dāng)需要使用到某個(gè)服務(wù)時(shí),skynet 會(huì)調(diào)用 skynet_context_new 函數(shù)為其創(chuàng)建上下文,這個(gè)過(guò)程當(dāng)中會(huì)調(diào)用 skynet_module_query(name) 函數(shù),該函數(shù)會(huì)根據(jù) name 查找相應(yīng)的模塊。如果該模塊尚未被加載,則將其加載到 modules 當(dāng)中。具體代碼如下
//skynet_module.c
//根據(jù)模塊名查找對(duì)應(yīng)的模塊,如果找不到且 modules 中尚有空間則將模塊加載進(jìn)來(lái)
struct skynet_module * skynet_module_query(const char * name) {
struct skynet_module * result = _query(name);
if (result)
return result;
SPIN_LOCK(M)
//雙重檢測(cè)可以避免以下情形:兩個(gè)不同的服務(wù) A 和 B 同時(shí)調(diào)用了一個(gè)服務(wù) C,在 A 查找 C 中的模塊時(shí),B 進(jìn)入自旋等待狀態(tài)。
//當(dāng) A 調(diào)用結(jié)束后會(huì)將 C 模塊插入 modules 中,此時(shí)如果 B 再執(zhí)行插入則會(huì)導(dǎo)致重復(fù)插入
result = _query(name); // double check
if (result == NULL && M->count < MAX_MODULE_TYPE) {
int index = M->count;
//返回相應(yīng)動(dòng)態(tài)庫(kù)的句柄
void * dl = _try_open(M,name);
if (dl) {
M->m[index].name = name;
M->m[index].module = dl;
if (open_sym(&M->m[index]) == 0) {
M->m[index].name = skynet_strdup(name);
M->count ++;
result = &M->m[index];
}
}
}
SPIN_UNLOCK(M)
return result;
}
static int open_sym(struct skynet_module *mod) {
mod->create = get_api(mod, "_create");
mod->init = get_api(mod, "_init");
mod->release = get_api(mod, "_release");
mod->signal = get_api(mod, "_signal");
return mod->init == NULL;
}
//從動(dòng)態(tài)庫(kù)中找到對(duì)應(yīng)的 api 并將其函數(shù)地址返回
static void* get_api(struct skynet_module *mod, const char *api_name) {
size_t name_size = strlen(mod->name);
size_t api_size = strlen(api_name);
char tmp[name_size + api_size + 1];
memcpy(tmp, mod->name, name_size);
memcpy(tmp+name_size, api_name, api_size+1);
char *ptr = strrchr(tmp, '.');
if (ptr == NULL) {
ptr = tmp;
} else {
ptr = ptr + 1;
}
return dlsym(mod->module, ptr);
}
從上述代碼中可以看出,加載模塊需要先調(diào)用 _try_open() 函數(shù)去打開(kāi)對(duì)應(yīng)的 .so 文件, 并通過(guò) open_sym 函數(shù)來(lái)將對(duì)應(yīng)的 api 存放到 module 結(jié)構(gòu)體中相應(yīng)的函數(shù)指針處。.so 文件中的 api 命名統(tǒng)一按照 "module_function" 的格式命名。
3.服務(wù)的啟動(dòng)
skynet 中服務(wù)的創(chuàng)建主要通過(guò) skynet_context_new 來(lái)完成,其代碼定義如下:
//skynet_server.c
struct skynet_context* skynet_context_new(const char * name, const char *param) {
struct skynet_module * mod = skynet_module_query(name);
if (mod == NULL)
return NULL;
void *inst = skynet_module_instance_create(mod);
if (inst == NULL)
return NULL;
struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
CHECKCALLING_INIT(ctx)
ctx->mod = mod;
ctx->instance = inst;
//此處將引用置為 2 的原因是因?yàn)樵?skynet_handle_register 中會(huì)將 ctx 保存起來(lái),增加一次引用。
//之后再將 ctx 返回給對(duì)應(yīng)的變量,增加了一次引用,因此 ref = 2
ctx->ref = 2;
ctx->cb = NULL;
ctx->cb_ud = NULL;
ctx->session_id = 0;
ctx->logfile = NULL;
ctx->init = false;
ctx->endless = false;
ctx->cpu_cost = 0;
ctx->cpu_start = 0;
ctx->message_count = 0;
ctx->profile = G_NODE.profile;
// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
ctx->handle = 0;
ctx->handle = skynet_handle_register(ctx);
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);
CHECKCALLING_END(ctx)
if (r == 0) {
//skynet_context_release 會(huì)在 ctx->ref == 0 時(shí)回收這個(gè) context
struct skynet_context * ret = skynet_context_release(ctx);
if (ret) {
ctx->init = true;
}
skynet_globalmq_push(queue);
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_context_new 的主要工作為如下:
- 在 modules 中查找對(duì)應(yīng)的模塊名稱(chēng),如果存在則直接返回模塊的句柄,不存在則將模塊加載進(jìn)內(nèi)存,并保存在 modules 當(dāng)中
- 調(diào)用 module 的 create api 創(chuàng)建 module 的實(shí)例 inst
- 分配 skynet_context 結(jié)構(gòu)體并為其賦上相應(yīng)的值
- 調(diào)用 module 的 init api 為 inst 進(jìn)行初始化
如果初始化成功,則將該 context 中的次級(jí)消息隊(duì)列 queue 放入到全局消息隊(duì)列當(dāng)中,然后返回創(chuàng)建好的服務(wù)(context)
如果失敗則釋放分配的 skynet_context, 為服務(wù)分配的 handle 以及專(zhuān)屬的次級(jí)消息隊(duì)列, 然后返回 NULL。
上述代碼中需要注意的,ctx->ref的初始值為 2。這是因?yàn)楫?dāng) skynet_context_new 執(zhí)行完畢后,會(huì)有兩個(gè)地方引用了新創(chuàng)建好的 context。一個(gè)是 skynet_context_new 的調(diào)用者,它會(huì)保存返回的 context 指針; 另一個(gè)則是 skynet_handle_register 函數(shù),該函數(shù)會(huì)將新創(chuàng)建的 context 保存在 handle_storage 的 slot 字段中
接下來(lái),我們來(lái)看看 skynet_context_new 中的幾個(gè)模塊相關(guān)的函數(shù):skynet_module_instance_create、 skynet_module_instance_init
//skynet_module.c
void* skynet_module_instance_create(struct skynet_module *m) {
if (m->create) {
return m->create();
} else {
return (void *)(intptr_t)(~0);
}
}
int skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm) {
return m->init(inst, ctx, parm);
}
void skynet_module_instance_release(struct skynet_module *m, void *inst) {
if (m->release) {
m->release(inst);
}
}
void skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal) {
if (m->signal) {
m->signal(inst, signal);
}
}
在上述代碼中,skynet_module_instance_create 的返回值 (void *)(intptr_t)(~0) 引起了我的好奇。這個(gè)地址的值為 0xffffffff, 代表的是內(nèi)存地址的最底端的地址。它主要的作用就是為了和 NULL 作區(qū)分。當(dāng) skynet 調(diào)用對(duì)應(yīng)模塊的 _create 函數(shù)時(shí), 如果此時(shí)內(nèi)存耗盡,無(wú)法創(chuàng)建模塊對(duì)象,則會(huì)返回 NULL。如果用戶(hù)在沒(méi)有定義 _create 函數(shù)情況下也使用 NULL 做返回值,則無(wú)法區(qū)分這兩種情況。
4.總結(jié)
簡(jiǎn)單地來(lái)講,skynet 的模塊加載與服務(wù)創(chuàng)建的整體過(guò)程為:
當(dāng) skynet 啟動(dòng)時(shí)會(huì)先執(zhí)行 skynet_module_init 進(jìn)行 modules 的創(chuàng)建,隨后調(diào)用 skynet_context_new 創(chuàng)建新的服務(wù)。在這個(gè)過(guò)程當(dāng)中, skynet 先會(huì)自動(dòng)根據(jù)配置文件中指定的模塊路徑進(jìn)行 module 的加載。完成加載后的 module 將被保存在全局的 modules 當(dāng)中。隨后,分配 skynet_context 結(jié)構(gòu)體并進(jìn)行相應(yīng)賦值。在賦值的過(guò)程中會(huì)調(diào)用到 module 的 _create, _init 等 api。如果分配成功則將 context 返回給調(diào)用者,失敗返回 NULL。創(chuàng)建好的服務(wù)彼此透明,運(yùn)行在不同的 skynet_context 下,不同的服務(wù)之間的交互必須通過(guò)消息隊(duì)列進(jìn)行轉(zhuǎn)發(fā)