skynet源码分析(1)--模块加载
来源:互联网 发布:苹果手机变音软件 编辑:程序博客网 时间:2024/05/19 02:19
作者: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 intopen_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); }}intskynet_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函数 }}voidskynet_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信号,告诉模块该停了。
- skynet源码分析(1)--模块加载
- skynet源码分析(11)--skynet的配置加载
- skynet源码分析(7)--skynet中的timer
- skynet源码分析(8)--skynet的网络
- skynet源码分析(4)--monitor
- skynet定时器源码分析
- skynet 源码分析
- skynet源码分析【skynet名字的管理】
- skynet框架 源码分析 一
- skynet框架 源码分析 二
- skynet框架 源码分析 三
- skynet框架 源码分析 四
- skynet框架 源码分析 五
- skynet消息队列源码分析
- skynet框架 源码分析 一
- skynet框架 源码分析 二
- skynet框架 源码分析 三
- skynet框架 源码分析 四
- HDU 2087 剪花布条【最长不重复子串】【KMP】【水题】【模板题】
- python居家旅行必备的pyenv,virtualenv
- HDU 6150 Vertex Cover 构造
- python试爬李毅吧贴子标题,爬虫最初级
- python使用scrapy爬表格,爬虫中级
- skynet源码分析(1)--模块加载
- org.springframework.beans.ConversionNotSupportedException异常解决方法
- skynet源码分析(2)--消息队列mq
- skynet源码分析(3)--消息名字和ID之handle
- Unix和Linux有什么区别? 通俗解释
- skynet源码分析(4)--monitor
- skynet源码分析(5)--消息机制之消息处理
- skynet源码分析(6)--消息机制之消息分发
- skynet源码分析(7)--skynet中的timer