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服务及一些初始化等。