skynet源碼分析(1)--模塊加載

作者:shihuaping0918@163.com观话,轉(zhuǎn)載請注明作者

兩個月前接觸skynet,最初使用的時候過程是相當痛苦的,而且網(wǎng)絡上可以找到的學習資料并不多权烧。當時決定寫一些skynet相關的文章,最近終于有空了檐束,開始寫這個東西辫秧。

skynet是云風開源的一個游戲框架,底層是c被丧,中間層和上層都是lua盟戏。基于actor模型甥桂,使用消息隊列進行內(nèi)部通信柿究。萬丈高樓平地起,先開始看最底層的內(nèi)容吧黄选,因為上層的會涉及一些業(yè)務蝇摸,而最底層的只涉及一些系統(tǒng)調(diào)用,理解起來更簡單办陷。

閱讀代碼使用的工具是eclipse cdt貌夕。代碼提交tag是f94ca6f

skynet底層代碼位于skynet/skynet-src下,模塊加載相關在skynet-module.c skynet-module.h這兩個文件里。這里的模塊在linux下指的是so民镜,在windows下指的是dll啡专,在skynet中指的是config中配置的cpath下的文件。

//以下四行為函數(shù)指針聲明
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);

//單個模塊的結(jié)構(gòu)體
struct skynet_module {
    const char * name; //模塊名
    void * module; //模塊指針
    skynet_dl_create create;  //create函數(shù)
    skynet_dl_init init; //init函數(shù)
    skynet_dl_release release; //release函數(shù)
    skynet_dl_signal signal; //signal函數(shù)
};

//添加一個模塊
void skynet_module_insert(struct skynet_module *mod);
//查詢一個模塊
struct skynet_module * skynet_module_query(const char * name);
//某個模塊中的create函數(shù)調(diào)用
void * skynet_module_instance_create(struct skynet_module *);
//某個模塊中的init函數(shù)調(diào)用
int skynet_module_instance_init(struct skynet_module *, void * inst, struct skynet_context *ctx, const char * parm);
//某個模塊中的release函數(shù)調(diào)用
void skynet_module_instance_release(struct skynet_module *, void *inst);
//某個模塊中的signal函數(shù)調(diào)用
void skynet_module_instance_signal(struct skynet_module *, void *inst, int signal);
//初始化模塊管理
void skynet_module_init(const char *path);

從上面的代碼可以看出制圈,每個模塊需要實現(xiàn)四個最基本的函數(shù)们童,create/init/release/signal。注意這里并不是說函數(shù)名字叫這個鲸鹦,函數(shù)名字具體叫什么下面會講到慧库。


#define MAX_MODULE_TYPE 32

//這里定義了模塊列表數(shù)據(jù)結(jié)構(gòu)
struct modules {
    int count;
    struct spinlock lock;
    const char * path;
    struct skynet_module m[MAX_MODULE_TYPE]; //最多只能加載32個模塊
};

static struct modules * M = NULL;

//內(nèi)部函數(shù),打開一個動態(tài)庫
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;
}

//根據(jù)模塊名在模塊列表中查找
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是一個系統(tǒng)函數(shù)完沪,根據(jù)函數(shù)名字獲取函數(shù)地址(指針)
    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;  //然而這里只判定只要實現(xiàn)了init就可以了
}

//根據(jù)模塊名查找模塊
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)

        //模塊是不是已經(jīng)在列表中了
    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(); //對應上文說的,調(diào)用模塊的create函數(shù)
    } 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); //對應上文說的,調(diào)用模塊的init函數(shù)
}

void 
skynet_module_instance_release(struct skynet_module *m, void *inst) {
    if (m->release) {
        m->release(inst); //對應上文說的覆积,調(diào)用模塊的release函數(shù)
    }
}

void
skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal) {
    if (m->signal) {
        m->signal(inst, signal); //對應上文說的听皿,調(diào)用模塊的release函數(shù)
    }
}

//初始化模塊列表數(shù)據(jù)結(jié)構(gòu)
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中被調(diào)用,傳進來的path是在運行時config中配置的宽档,如果config文件中沒有配置cpath尉姨,默認將cpath的值設為./cservice/?.so,加載cpath目錄下的so文件吗冤。

從get_api可以看出來又厉,skynet要求模塊的create/init/release/signal方法的命名是模塊名加一個下劃線,后面帶create/init/release/signal椎瘟。在skynet/service-src目錄下有現(xiàn)成的例子覆致,大家可以去看一下。

到這里肺蔚,整個模塊加載功能就分析完了煌妈。從啟動流程來分析是,首先在config文件中配置一個cpath宣羊,它包含了你想要加載的so的路徑璧诵。然后skynet-main.c在啟動的時候會把cpath讀出來,設進moduls->path中仇冯。在skynet-server.c中的skynet_context_new中會調(diào)用skynet_module_query之宿,skynet_module_query首先會在列表中查詢so是否已經(jīng)加載,如果沒有就直接加載它苛坚。

模塊一定要包含有四個函數(shù)init/create/release/signal比被,它的命名格式為,假定模塊名為xxx炕婶,那么就是xxx_create/xxx_init/xxx_release/xxx_signal姐赡。這四個函數(shù)是干嘛用的?

create做內(nèi)存分配柠掂。init做初始化项滑,它可能會做一些其它的事情,比如打開網(wǎng)絡涯贞,打開文件枪狂,函數(shù)回調(diào)掛載等等。relase做資源回收宋渔,包括內(nèi)存資源州疾,文件資源,網(wǎng)絡資源等等皇拣,signal是發(fā)信號严蓖,比如kill信號薄嫡,告訴模塊該停了。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颗胡,一起剝皮案震驚了整個濱河市毫深,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毒姨,老刑警劉巖哑蔫,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弧呐,居然都是意外死亡闸迷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門俘枫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腥沽,“玉大人,你說我怎么就攤上這事鸠蚪⊙睬颍” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵邓嘹,是天一觀的道長。 經(jīng)常有香客問我险胰,道長汹押,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任起便,我火速辦了婚禮棚贾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榆综。我一直安慰自己妙痹,他們只是感情好,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布鼻疮。 她就那樣靜靜地躺著怯伊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪判沟。 梳的紋絲不亂的頭發(fā)上耿芹,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音挪哄,去河邊找鬼吧秕。 笑死,一個胖子當著我的面吹牛迹炼,可吹牛的內(nèi)容都是我干的砸彬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼砂碉!你這毒婦竟也來了蛀蜜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤绽淘,失蹤者是張志新(化名)和其女友劉穎涵防,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沪铭,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡壮池,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杀怠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椰憋。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赔退,靈堂內(nèi)的尸體忽然破棺而出橙依,到底是詐尸還是另有隱情,我是刑警寧澤硕旗,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布窗骑,位于F島的核電站,受9級特大地震影響漆枚,放射性物質(zhì)發(fā)生泄漏创译。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一墙基、第九天 我趴在偏房一處隱蔽的房頂上張望软族。 院中可真熱鬧,春花似錦残制、人聲如沸立砸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颗祝。三九已至,卻和暖如春恼布,著一層夾襖步出監(jiān)牢的瞬間吐葵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工桥氏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留温峭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓字支,卻偏偏與公主長得像凤藏,于是被迫代替她去往敵國和親奸忽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評論 25 707
  • 1三個相關數(shù)據(jù)結(jié)構(gòu). 關于socket的創(chuàng)建揖庄,首先需要分析socket這個結(jié)構(gòu)體栗菜,這是整個的核心。 104 str...
    ice_camel閱讀 2,802評論 1 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蹄梢,服務發(fā)現(xiàn)疙筹,斷路器,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • 動態(tài)鏈接禁炒,在可執(zhí)行文件裝載時或運行時而咆,由操作系統(tǒng)的裝載程序加載庫。大多數(shù)操作系統(tǒng)將解析外部引用(比如庫)作為加載過...
    小5筒閱讀 5,476評論 0 3
  • 作者:魚腸劍 落雨紛紛幕袱,為濕幾重暴备。晴云叆叇,卻有離情们豌。繁花蔭里涯捻,烏鵲悄聲。早蟬未鳴望迎,難免清冷障癌。戶有南牖,所隔萬重辩尊。...
    heyeyes閱讀 506評論 0 1