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编写的服务就启动起来了。

 

 风潇潇

 

posted @ 2015-02-06 14:41  okcc  阅读(6878)  评论(0编辑  收藏  举报