skynet啟動過程_1


skynet的啟動時需帶個配置文件,這個文件其實是作為lua全局變量用的,見
 
int
main(int argc, char *argv[]) {
     const char * config_file = NULL ;
     if (argc > 1) {
          config_file = argv[1];
     } else {
          fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
               "usage: skynet configfilename\n");
          return 1;
     }
     skynet_globalinit();
     skynet_env_init();

     sigign();

     struct skynet_config config;

     struct lua_State *L = lua_newstate(skynet_lalloc, NULL);
     luaL_openlibs(L);     // link lua lib

     int err = luaL_loadstring(L, load_config);
     assert(err == LUA_OK);
     lua_pushstring(L, config_file);
    
     err = lua_pcall(L, 1, 1, 0);
     if (err) {
          fprintf(stderr,"%s\n",lua_tostring(L,-1));
          lua_close(L);
          return 1;
     }
     _init_env(L);

     config.thread =  optint("thread",8);
     config.module_path = optstring("cpath","./cservice/?.so");
     config.harbor = optint("harbor", 1);
     config.bootstrap = optstring("bootstrap","snlua bootstrap");
     config.daemon = optstring("daemon", NULL);
     config.logger = optstring("logger", NULL);

     lua_close(L);

     skynet_start(&config);
     skynet_globalexit();

     return 0;
}

配置了一些基本的環境變量后,轉到skynet_start方法,開始啟動skynet,在skynet_start方法中初始化一些變量后,系統啟動的第一個服務是logger:

 struct skynet_context *ctx = skynet_context_new("logger", config->logger);
     if (ctx == NULL) {
          fprintf(stderr, "Can't launch logger service\n");
          exit(1);
     }
skynet通過skynet_context_new函數來實例化一個服務:先是從logger.so文件把模塊加載進來;
 
    struct skynet_module * mod = skynet_module_query(name);

     if (mod == NULL)
          return NULL;
讓模塊自生成一個新的實例;
 
     void *inst = skynet_module_instance_create(mod);
分配一個新的handle;
 
     ctx->handle = skynet_handle_register(ctx);
初始化一個消息隊列;
 
     struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
調用這個模塊的初始化方法
 
     int r = skynet_module_instance_init(mod, inst, ctx, param);
最后是把自己的消息隊列加入到全局消息隊列中,把有加入到全局的消息隊列后,才能收到消息回調
 
     skynet_globalmq_push(queue);

啟動完成logger服務后,系統接下來要啟動的服務是bootstrap,但先要加載snlua模塊,所有的lua服務都屬於snlua模塊的實例。

static void
bootstrap(struct skynet_context * logger, const char * cmdline) {
     int sz = strlen(cmdline);
     char name[sz+1];
     char args[sz+1];
     sscanf(cmdline, "%s %s", name, args);
     struct skynet_context *ctx = skynet_context_new(name, args);
     if (ctx == NULL) {
          skynet_error(NULL, "Bootstrap error : %s\n", cmdline);
          skynet_context_dispatchall(logger);
          exit(1);
     }
}
其中參數cmdline是在config配置里的
bootstrap = "snlua bootstrap"   
 
和加載logger服務類似,先是把snlua.so文件作為模塊加載進來,調用模塊自身的_create函數產生一個snlua實例,在service_snlua.c文件中。
struct snlua *
snlua_create(void) {
     struct snlua * l = skynet_malloc(sizeof(*l));
     memset(l,0,sizeof(*l));
     l->L = lua_newstate(skynet_lalloc, NULL);
     return l;
}
在方法中啟動了新生成了一個lua VM,出就是lua沙盒環境,這一點也比較重要,因為所有的lua服務都是是一個獨立的VM中運行的,這也是雲風的設計初衷。
 
接下來就會調用了service_snlua.c中的snlua_init方法
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
     int sz = strlen(args);
     char * tmp = skynet_malloc(sz);
     memcpy(tmp, args, sz);
     skynet_callback(ctx, l , _launch);
     const char * self = skynet_command(ctx, "REG", NULL);
     uint32_t handle_id = strtoul(self+1, NULL, 16);
     // it must be first message
     skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
     return 0;
}
來初始化服務,在這個方法中做了兩件事情;
注冊了一個回調函數,當有消息到來時,這個函數會被調用
skynet_callback(ctx, l , _launch);
向自己發送了一條消息,並附帶了一個參數,這個參數就是bootstrap。當把消息隊列加入到全局隊列后,收到的第一條消息就是這條消息。
收到第一條消息后,調用到callback函數,也就是service_snlua.c里的_launch方法
 
static int
_launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
     assert(type == 0 && session == 0);
     struct snlua *l = ud;
     skynet_callback(context, NULL, NULL);
     int err = _init(l, context, msg, sz);
     if (err) {
          skynet_command(context, "EXIT", NULL);
     }

     return 0;
}
這個方法里把自己的回調函數給注銷了,使它不再接收消息,為的是在lua層重新注冊它,把消息通過lua接口來接收。緊接着執行_init方法。在_init方法里設置了一些虛擬機環境變量后,就加載執行了loader.lua文件,同時要把真正要加載的文件(這個時候是bootstrap)作為參數傳給它, 控制權就開始轉到lua層。
loader.lua是用來加載lua文件的,在loader.lua中會判斷是否需要preload,最終會加載執行bootstrap.lua文件:
local skynet = require "skynet"
local harbor = require "skynet.harbor"

skynet.start(function()
     local standalone = skynet.getenv "standalone"

     local launcher = assert(skynet.launch("snlua","launcher"))
     skynet.name(".launcher", launcher)

     local harbor_id = tonumber(skynet.getenv "harbor")
     if harbor_id == 0 then
          assert(standalone ==  nil)
          standalone = true
          skynet.setenv("standalone", "true")

          local ok, slave = pcall(skynet.newservice, "cdummy")
          if not ok then
               skynet.abort()
          end
          skynet.name(".cslave", slave)

     else
          if standalone then
               if not pcall(skynet.newservice,"cmaster") then
                    skynet.abort()
               end
          end

          local ok, slave = pcall(skynet.newservice, "cslave")
          if not ok then
               skynet.abort()
          end
          skynet.name(".cslave", slave)
     end

     if standalone then
          local datacenter = skynet.newservice "datacenterd"
          skynet.name("DATACENTER", datacenter)
     end
     skynet.newservice "service_mgr"
     pcall(skynet.newservice,skynet.getenv "start" or "main")
     skynet.exit()
end)

在這個文件里啟動了其它一些服務,這些暫不看,在這個文件里調用了服務啟動的接口skynet.start。這也是所有lua服務的標准啟動入口,參數是一個回調方法,服務啟動完成后會調到這個方法。做一些初始化的工作。

skynet.lua文件的start方法:
 
function skynet.start(start_func)
     c.callback(dispatch_message)
     skynet.timeout(0, function()
          init_service(start_func)
     end)
end
通過
c.callback(dispatch_message)
重新注冊了callback函數,這樣就能在lua接收消息了。收到消息時,通過dispatch_message方法來分發。
c.callback調用的是一個c函數,在lua-skynet.c文件的_callback方法。
static int
_callback(lua_State *L) {
     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
     int forward = lua_toboolean(L, 2);
     luaL_checktype(L,1,LUA_TFUNCTION);
     lua_settop(L,1);
     lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);

     lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
     lua_State *gL = lua_tothread(L,-1);

     if (forward) {
          skynet_callback(context, gL, forward_cb);
     } else {
          skynet_callback(context, gL, _cb);
     }

     return 0;
}
在這個方法中可以看到,重新調用了skynet_callback來注冊服務的回調函數。
到此,一個lua編寫的服務就啟動起來了。

 

 風瀟瀟

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM