skynet原始碼分析(1)--模組載入
作者:shihuaping0918@163.com,轉載請註明作者
兩個月前接觸skynet,最初使用的時候過程是相當痛苦的,而且網路上可以找到的學習資料並不多。當時決定寫一些skynet相關的文章,最近終於有空了,開始寫這個東西。
skynet是雲風開源的一個遊戲框架,底層是c,中間層和上層都是lua。基於actor模型,使用訊息佇列進行內部通訊。萬丈高樓平地起,先開始看最底層的內容吧,因為上層的會涉及一些業務,而最底層的只涉及一些系統呼叫,理解起來更簡單。
閱讀程式碼使用的工具是eclipse cdt。程式碼提交tag是f94ca6f
skynet底層程式碼位於skynet/skynet-src下,模組載入相關在skynet-module.c skynet-module.h這兩個檔案裡。這裡的模組在linux下指的是so,在windows下指的是dll,在skynet中指的是config中配置的cpath下的檔案。
//以下四行為函式指標宣告
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; //模組名
void * module; //模組指標
skynet_dl_create create; //create函式
skynet_dl_init init; //init函式
skynet_dl_release release; //release函式
skynet_dl_signal signal; //signal函式
};
//新增一個模組
void skynet_module_insert(struct skynet_module *mod);
//查詢一個模組
struct skynet_module * skynet_module_query(const char * name);
//某個模組中的create函式呼叫
void * skynet_module_instance_create(struct skynet_module *);
//某個模組中的init函式呼叫
int skynet_module_instance_init(struct skynet_module *, void * inst, struct skynet_context *ctx, const char * parm);
//某個模組中的release函式呼叫
void skynet_module_instance_release(struct skynet_module *, void *inst);
//某個模組中的signal函式呼叫
void skynet_module_instance_signal(struct skynet_module *, void *inst, int signal);
//初始化模組管理
void skynet_module_init(const char *path);
從上面的程式碼可以看出,每個模組需要實現四個最基本的函式,create/init/release/signal。注意這裡並不是說函式名字叫這個,函式名字具體叫什麼下面會講到。
#define MAX_MODULE_TYPE 32
//這裡定義了模組列表資料結構
struct modules {
int count;
struct spinlock lock;
const char * path;
struct skynet_module m[MAX_MODULE_TYPE]; //最多隻能載入32個模組
};
static struct modules * M = NULL;
//內部函式,開啟一個動態庫
static void *
_try_open(struct modules *m, const char * name) {
const char *l;
const char * path = m->path;
size_t path_size = strlen(path);
size_t name_size = strlen(name);
int sz = path_size + name_size;
//search path
void * dl = NULL;
char tmp[sz];
//遍歷路徑查詢so,路徑以;分隔
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);
}
//dlope開啟so
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 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];
//將模組名附到tmp中
memcpy(tmp, mod->name, name_size);
//將方法名附到tmp中
memcpy(tmp+name_size, api_name, api_size+1);
char *ptr = strrchr(tmp, '.');
if (ptr == NULL) {
ptr = tmp;
} else {
ptr = ptr + 1;
}
// dlsym是一個系統函式,根據函式名字獲取函式地址(指標)
return dlsym(mod->module, ptr);
}
static int
open_sym(struct skynet_module *mod) {
mod->create = get_api(mod, "_create"); //獲取create方法
mod->init = get_api(mod, "_init"); //獲取init方法
mod->release = get_api(mod, "_release"); //獲取release方法
mod->signal = get_api(mod, "_signal"); //獲取signal方法
return mod->init == NULL; //然而這裡只判定只要實現了init就可以了
}
//根據模組名查詢模組
struct skynet_module *
skynet_module_query(const char * name) {
//先到列表裡查
struct skynet_module * result = _query(name);
if (result)
return result;
SPIN_LOCK(M)
result = _query(name); // double check
//在列表裡沒查到
if (result == NULL && M->count < MAX_MODULE_TYPE) {
int index = M->count;
//開啟so
void * dl = _try_open(M,name);
if (dl) {
M->m[index].name = name;
M->m[index].module = dl;
//獲取so中的init/create/release/signal方法地址
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;
}
//新增模組到模組列表
void
skynet_module_insert(struct skynet_module *mod) {
SPIN_LOCK(M)
//模組是不是已經在列表中了
struct skynet_module * m = _query(mod->name);
assert(m == NULL && M->count < MAX_MODULE_TYPE);
int index = M->count;
M->m[index] = *mod;
++M->count;
SPIN_UNLOCK(M)
}
void *
skynet_module_instance_create(struct skynet_module *m) {
if (m->create) {
return m->create(); //對應上文說的,呼叫模組的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); //對應上文說的,呼叫模組的init函式
}
void
skynet_module_instance_release(struct skynet_module *m, void *inst) {
if (m->release) {
m->release(inst); //對應上文說的,呼叫模組的release函式
}
}
void
skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal) {
if (m->signal) {
m->signal(inst, signal); //對應上文說的,呼叫模組的release函式
}
}
//初始化模組列表資料結構
void
skynet_module_init(const char *path) {
struct modules *m = skynet_malloc(sizeof(*m));
m->count = 0;
m->path = skynet_strdup(path);
SPIN_INIT(m)
M = m;
}
skynet_module_init在skynet-main.c中被呼叫,傳進來的path是在執行時config中配置的,如果config檔案中沒有配置cpath,預設將cpath的值設為./cservice/?.so,載入cpath目錄下的so檔案。
從get_api可以看出來,skynet要求模組的create/init/release/signal方法的命名是模組名加一個下劃線,後面帶create/init/release/signal。在skynet/service-src目錄下有現成的例子,大家可以去看一下。
到這裡,整個模組載入功能就分析完了。從啟動流程來分析是,首先在config檔案中配置一個cpath,它包含了你想要載入的so的路徑。然後skynet-main.c在啟動的時候會把cpath讀出來,設進moduls->path中。在skynet-server.c中的skynet_context_new中會呼叫skynet_module_query,skynet_module_query首先會在列表中查詢so是否已經載入,如果沒有就直接載入它。
模組一定要包含有四個函式init/create/release/signal,它的命名格式為,假定模組名為xxx,那麼就是xxx_create/xxx_init/xxx_release/xxx_signal。這四個函式是幹嘛用的?
create做記憶體分配。init做初始化,它可能會做一些其它的事情,比如開啟網路,開啟檔案,函式回撥掛載等等。relase做資源回收,包括記憶體資源,檔案資源,網路資源等等,signal是發訊號,比如kill訊號,告訴模組該停了。
相關文章
- nodejs模組載入分析(1).mdNodeJS
- Layui 原始碼淺讀(模組載入原理)UI原始碼
- 結合原始碼分析 Node.js 模組載入與執行原理原始碼Node.js
- JVM類載入器-原始碼分析JVM原始碼
- 圖片載入框架Picasso - 原始碼分析框架原始碼
- 圖片載入框架Picasso原始碼分析框架原始碼
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- Zepto原始碼分析之form模組原始碼ORM
- 精盡Spring Boot原始碼分析 - 配置載入Spring Boot原始碼
- Springboot 載入配置檔案原始碼分析Spring Boot原始碼
- [9]elasticsearch原始碼深入分析——Plugin元件載入Elasticsearch原始碼Plugin元件
- 【MyBatis原始碼分析】Configuration載入(下篇)MyBatis原始碼
- 【MyBatis原始碼分析】Configuration載入(上篇)MyBatis原始碼
- Symfony2.8 原始碼分析之類的載入原始碼
- Android圖片載入庫Picasso原始碼分析Android原始碼
- Leveldb原始碼分析--1原始碼
- MYSQL原始碼分析1MySql原始碼
- 介面1原始碼分析原始碼
- Swoole 原始碼分析——Client模組之Recv原始碼client
- Swoole 原始碼分析——Client模組之Send原始碼client
- Django(49)drf解析模組原始碼分析Django原始碼
- Django(51)drf渲染模組原始碼分析Django原始碼
- 從原始碼分析Node的Cluster模組原始碼
- Giraph 原始碼分析(五)—— 載入資料+同步總結原始碼
- linux原始碼分析1Linux原始碼
- Hadoop2原始碼分析-HDFS核心模組分析Hadoop原始碼
- (一) Mybatis原始碼分析-解析器模組MyBatis原始碼
- Swoole 原始碼分析——Client模組之Connect原始碼client
- Swoole 原始碼分析——Server模組之OpenSSL (上)原始碼Server
- JavaScript 模組化及 SeaJs 原始碼分析JavaScriptJS原始碼
- Django(48)drf請求模組原始碼分析Django原始碼
- mybaits原始碼分析--日誌模組(四)AI原始碼
- mybaits原始碼分析--快取模組(六)AI原始碼快取
- mybaits原始碼分析--binding模組(五)AI原始碼
- Swoole 原始碼分析——Server 模組之 OpenSSL (上)原始碼Server
- Swoole 原始碼分析——Server 模組之 OpenSSL (下)原始碼Server
- beego cache模組原始碼分析筆記四Go原始碼筆記
- Swoole 原始碼分析——Reactor 模組之 ReactorEpoll原始碼React