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信号,告诉模块该停了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,469评论 25 707
  • 1三个相关数据结构. 关于socket的创建,首先需要分析socket这个结构体,这是整个的核心。 104 str...
    ice_camel阅读 2,802评论 1 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载库。大多数操作系统将解析外部引用(比如库)作为加载过...
    小5筒阅读 5,474评论 0 3
  • 作者:鱼肠剑 落雨纷纷,为湿几重。晴云叆叇,却有离情。繁花荫里,乌鹊悄声。早蝉未鸣,难免清冷。户有南牖,所隔万重。...
    heyeyes阅读 505评论 0 1