skynet 源碼閱讀筆記 —— skynet 的模塊與服務(wù)

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 的主要工作為如下:

  1. 在 modules 中查找對(duì)應(yīng)的模塊名稱(chēng),如果存在則直接返回模塊的句柄,不存在則將模塊加載進(jìn)內(nèi)存,并保存在 modules 當(dāng)中
  2. 調(diào)用 module 的 create api 創(chuàng)建 module 的實(shí)例 inst
  3. 分配 skynet_context 結(jié)構(gòu)體并為其賦上相應(yīng)的值
  4. 調(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_storageslot 字段中
接下來(lái),我們來(lái)看看 skynet_context_new 中的幾個(gè)模塊相關(guān)的函數(shù):skynet_module_instance_createskynet_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ā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容