skynet的启动 <1>

skynet

skynet的启动

skynet_main中的main函数作为进入点,需将配置文件config路径传进去(因此启动时候命令为:./skynet ./路径/config)。函数中进行全局初始化及信号忽略。将配置作为参数调用skynet_start()函数。配置参数结构如下:

struct skynet_config {
	int thread;			// 线程数
	int harbor;			// 集群结点ID
	int profile;		// TODO?
	const char * daemon;			// 守护进程 
	const char * module_path;		// 模块路径
	const char * bootstrap;			// skynet 启动的第一个服务以及其启动参数默认配置为 snlua bootstrap ,
									// 即启动一个名为 bootstrap 的 lua 服务。通常指的是 service/bootstrap.lua 这段代码。
	const char * logger;			// 日志文件路径,如果为NULL,则输出到stdout
	const char * logservice;		// 日志服务的模块名,默认为logger
};

skynet_start.c

    // 初始化集群模块
    skynet_harbor_init(config->harbor);
	// 初始化句柄模块
	skynet_handle_init(config->harbor);
	// 初始化消息队列
	skynet_mq_init();
	// 初始化服务动态库加载模块
	skynet_module_init(config->module_path);
	// 初始化定时器
	skynet_timer_init();
	// 初始化socket
	skynet_socket_init();
	// 是否启用profile,用于统计各模块cpu时间
	skynet_profile_enable(config->profile);

紧接着创建一个logger服务:

struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);

关于skynet_context_new服务的创建流程如下:

  • 加载服务模块,在skynet_module_query中查询,先查找全局变量中有没有,如没有再通过_try_open创建一个新模块,做法是到modules的path中去查找对应的so库,创建一个skynet_module对象,将so库加载到内存,并将访问该so库的句柄和skynet_module对象关联,并将so库中的xxx_create,xxx_init,xxx_signal,xxx_release四个函数地址赋值给skynet_module的create、init、signal和release四个函数中,这样这个skynet_module对象,就能调用so库中,对应的四个接口(_open_sym做了这件事)。如logger服务对应的借口为logger_create、create_init…
  • 由上面生成的模块,创建一个服务实例即skynet_context对象,他包含一个次级消息队列指针,服务模块指针(skynet_module对象,便于他访问module自定义的create、init、signal和release函数),由服务模块调用create接口创建的数据实例等
// 服务的上下文环境
struct skynet_context {
	void * instance;				// 服务模块的实例指针
	struct skynet_module * mod;		// 服务模块指针
	void * cb_ud;					// 回调函数的用户数据
	skynet_cb cb;					// 服务处理消息的回调函数
	struct message_queue *queue;	// 消息队列
	FILE * logfile;					// 该服务的日志文件句柄,调用cmd_logon后可打开,用于调试服务的消息
	uint64_t cpu_cost;	// in microsec	// profile:使用的CPU时间
	uint64_t cpu_start;	// in microsec	// profile:
	char result[32];				// 用于存放一些参数结果,用字符串的形式保存着
	uint32_t handle;				// 服务句柄
	int session_id;					// 用于生成sessionid的计数
	int ref;						// 引用计数,当计数为0,该服务释放
	int message_count;				// profile:统计处理了多少消息
	bool init;						// 服务是否初始化完毕
	bool endless;					// 服务是否处于死循环中,见skynet_monitor
	bool profile;					// 是否开户性能分析

	CHECKCALLING_DECL
}; 
  • 将该实例注册到全局服务列表中(详见skynet_handle_register),接着创建该实例的消息队列
// 句柄存储结构
struct handle_storage {
	struct rwlock lock;				// 读写锁

	uint32_t harbor;				// 集群ID
	uint32_t handle_index;			// 当前句柄索引,作为查找可用句柄的起始索引,见skynet_handle_register注解
	int slot_size;					// 桶数组大小
	struct skynet_context ** slot;	// 桶数组,元素为skynet_context
	
	int name_cap;					// 名字数组容量
	int name_count;					// 名字数组大小
	struct handle_name *name;		// 名字数组:是一个有序的数组,查找句柄使用二分查找法
};

// 消息队列
struct message_queue {
	struct spinlock lock;
	uint32_t handle;				// 关联的服务句柄
	int cap;						// 队列容量
	int head;						// 队列头的位置:用数组模拟环形的队列
	int tail;						// 队列尾的位置
	int release;					// 标记该队列是否为释放状态,1为释放
	int in_global;					// 标识是否在全局队列中,见MQ_IN_GLOBAL
	int overload;					// 过载的消息数量 
	int overload_threshold;			// 过载的阀值,被初始化为MQ_OVERLOAD
	struct skynet_message *queue;	// 消息结构数组
	struct message_queue *next;		// 指向下一个消息队列 
};
  • 进入模块初始化,设置消息处理的回调函数。
  • 将该实例的消息队列push进全局消息队列中
    以上便是服务的创建流程,之后其他服务发送给该服务的消息将被pop出来,并通过该服务的回调函数,从而达到驱动服务的目的。

创建完logger服务之后创建引导服务:bootstrap(ctx, config->bootstrap);流程与上面创建logger服务大同小异。通常通过这个服务把整个系统启动起来。默认的 bootstrap 配置项为 “snlua bootstrap” ,这意味着,skynet 会启动 snlua 这个服务,并将 bootstrap 作为参数传给它。snlua 是 lua 沙盒服务,bootstrap 会根据配置的 luaservice 匹配到最终的 lua 脚本。按照目录最终会匹配到bootstrap.lua脚本。(详见 https://github.com/cloudwu/skynet/wiki/Bootstrap)

接下来启动线程,共有监控线程、时间线程、socket线程、工作线程4种。前3个是固定的,worker线程是可配的。

  • socket线程:启动后会循环检测socket_server有没有消息,如果有则将该消息push到队列中。
  • time线程:每0.0025s进行唤醒worker线程操作
  • worker线程: 对消息进行处理。调用skynet_context_message_dispatch()函数,从全局消息队列中pop出一次级消息队列,根据次级消息的handle,找出其所属的服务(一个skynet_context实例)指针,再从次级消息队列中pop若干条消息,如有消息则触发对应的消息回调函数,从而实现消息处理的机制。该队列消息处理完后(根据权重)再将队列push回全局队列。

以上我们可知service-src目录是依附于skynet核心模块的c服务,如用于日志输出的logger服务,用于运行lua脚本snlua的c服务及一些初始化等。

你可能感兴趣的:(skynet,skynet)