初始化配置之后,就要开始启动skynet的主要模块。不过,启动模块之前要先初始化一些基本信息。
//初始化基本信息
skynet_harbor_init(config->harbor); //生成harbor ID 高八位表示 所以master/harbor模式下同个集群里最多只有256台harbor
skynet_handle_init(config->harbor); //句柄池初始化,管理服务对象句柄(地址),以及地址和服务的映射关系。
skynet_mq_init(); //一级消息队列初始化(skynet采用的是两级消息队列模式)
skynet_module_init(config->module_path); // module接口,这里主要存储加载到skynet中的c库,并对外提供c库方法的接口。这个可以看做是一个接口类,每加载一个c库相当于实例化一个继承该接口的对象。
skynet_timer_init(); // 定时器模块初始化。
skynet_socket_init(); // 网络模块初始化,这个并非skynet的网络库,这里主要是创建一个管道,作为服务对象和网络IO的桥梁从避免IO阻塞对服务器对象的影响。
skynet_profile_enable(config->profile); // debug
日志服务:主要负责输出与记录,程序运行中的日志。通用引导服务:用于拉起其他的服务。
struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger); // 生成日志服务Actor
bootstrap(ctx, config->bootstrap); //生成通用lua引导服务Actor
create_thread(&pid[0], thread_monitor, m); //启动监视模块
监视器的作用监控工作模块(其中的工作线程)对可能出现的死循环通过打印信息发出警告。下面是monitor的数据结构。
struct skynet_monitor {
int version; // 版本号 -- 其实就是消息的流水号
int check_version; // 检查版本号
uint32_t source; // 源地址
uint32_t destination; // 目标地址
};
监视器的工作方式,工作线程每次处理消息之前,version自增,记录源地址(source)和目标地址(destination),然后5秒钟检测一次目标地址是否为空(处理完消息之后置空源地址)且version与check_version相等则提示该工作线程可能死循环(因为该消息已经占用工作线程5秒)。
thread_monitor中每次检查间隔5秒。
for (i=0;i<5;i++) { // 休息5秒
CHECK_ABORT
sleep(1);
}
监视器的消息版本号更新与检测。
void
skynet_monitor_trigger(struct skynet_monitor *sm, uint32_t source, uint32_t destination) {
sm->source = source;
sm->destination = destination;
ATOM_INC(&sm->version);
}
void
skynet_monitor_check(struct skynet_monitor *sm) {
if (sm->version == sm->check_version) { // 版本一致
if (sm->destination) {
skynet_context_endless(sm->destination); //检查目标服务的引用,引用为0则回收,释放工作线程
skynet_error(NULL, "A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)", sm->source , sm->destination, sm->version);
}
} else {
sm->check_version = sm->version; // 不一致的时候更新
}
}
create_thread(&pid[1], thread_timer, m); //启动定时器模块
skynet的主要模块之一,为服务器提供基础的定时器功能。skynet的定时器是通过计数器的方式实现的,把时间段分成5级,一级为时间近点,即将发生超时的时间段,等待通知处理。后面4级是时间远点,随着时间增长一级一级向前移动直至近点,等待处理。下面是主要的数据结构:
//定时事件
struct timer_event {
uint32_t handle; // 服务地址
int session; // 回话ID
};
//定时节点
struct timer_node {
struct timer_node *next;
uint32_t expire;
};
//链表
struct link_list {
struct timer_node head;
struct timer_node *tail;
};
struct timer {
struct link_list near[TIME_NEAR]; // 时间近点哈希链表
struct link_list t[4][TIME_LEVEL];// 时间4级远点哈希链表
struct spinlock lock;
uint32_t time;
uint32_t starttime;
// 两个时间都是10ms一滴答
uint64_t current; // 系统时间
uint64_t current_point; // 服务器时间:相对于服务器重启的时间
};
static void
add_node(struct timer *T,struct timer_node *node) {
uint32_t time=node->expire;
uint32_t current_time=T->time;
if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) { // 近点时间槽
link(&T->near[time&TIME_NEAR_MASK],node);
} else {
int i;
uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
for (i=0;i<3;i++) {
// 根据分段找出远点4级时间槽的落点
if ((time|(mask-1))==(current_time|(mask-1))) {
break;
}
mask <<= TIME_LEVEL_SHIFT;
}
// 加入对应的时间槽
link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
}
}
static void
timer_add(struct timer *T,void *arg,size_t sz,int time) {
struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
memcpy(node+1,arg,sz);
SPIN_LOCK(T); // 加锁
node->expire=time+T->time; // 计算出超时时间点
add_node(T,node);
SPIN_UNLOCK(T); // 解锁
}
int
skynet_timeout(uint32_t handle, int time, int session) {
if (time <= 0) { // 超时时间到 直接发送消息
struct skynet_message message;
message.source = 0;
message.session = session;
message.data = NULL;
message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
if (skynet_context_push(handle, &message)) {
return -1;
}
} else {
struct timer_event event;
event.handle = handle;
event.session = session;
timer_add(TI, &event, sizeof(event), time); // 加入5级时间槽
}
return session;
}
#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK (TIME_NEAR-1) // 近点时间掩码
#define TIME_LEVEL_MASK (TIME_LEVEL-1) // 远点4级时间分段掩码
// 移动时间槽
static void
timer_shift(struct timer *T) {
int mask = TIME_NEAR;
uint32_t ct = ++T->time; // 滴答自增
// 溢出,移动最后一个时间分段
if (ct == 0) {
move_list(T, 3, 0);
} else {
uint32_t time = ct >> TIME_NEAR_SHIFT;
int i=0;
// 6/6/6/6 远点4级,滴答增加引起变化的时间分段,前向移动
while ((ct & (mask-1))==0) {
int idx=time & TIME_LEVEL_MASK;
if (idx!=0) {
move_list(T, i, idx);
break;
}
mask <<= TIME_LEVEL_SHIFT;
time >>= TIME_LEVEL_SHIFT;
++i;
}
}
}
// 执行
static inline void
timer_execute(struct timer *T) {
int idx = T->time & TIME_NEAR_MASK;
// 执行时间近点超时事件通知
while (T->near[idx].head.next) {
struct timer_node *current = link_clear(&T->near[idx]); // 清除超时时间事件
SPIN_UNLOCK(T);
// dispatch_list don't need lock T
dispatch_list(current);
SPIN_LOCK(T);
}
}
static void
timer_update(struct timer *T) {
SPIN_LOCK(T);
// try to dispatch timeout 0 (rare condition)
timer_execute(T); // 执行
// shift time first, and then dispatch timer message
timer_shift(T); // 移动
timer_execute(T); // 执行
SPIN_UNLOCK(T);
}
void
skynet_updatetime(void) {
uint64_t cp = gettime();
if(cp < TI->current_point) { // 时钟混乱
skynet_error(NULL, "time diff error: change from %lld to %lld", cp, TI->current_point);
TI->current_point = cp;
} else if (cp != TI->current_point) {
uint32_t diff = (uint32_t)(cp - TI->current_point); // 每次唤醒与上次的时间差(10ms一个滴答)
TI->current_point = cp;
TI->current += diff;
int i;
for (i=0;i<diff;i++) {
timer_update(TI);
}
}
}
create_thread(&pid[2], thread_socket, m); //启动网络模块
网络模块比较复杂,提供了tcp,udp的网络的接口,内容比较多且与lua层交互密切,后面单独章节讲述。
create_thread(&pid[i+3], thread_worker, &wp[i]); // 启动工作线程
工作模块根据配置的thread,启动相应的数量的工作线程。skynet的业务逻辑的基本单位是服务,每个服务对象都是嵌入式语言(这里指lua)的宿主主机,但是服务的本质计算能力是由工作线程提供的。服务的调度方式采用先到先得,没有优先级,不让出,所以服务阻塞,会影响的整个skynet的计算能力,毕竟工作线程的数量是有限的。工作线程通过处理两级队列的方式驱动服务的运行。
struct message_queue *
skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {
if (q == NULL) {
q = skynet_globalmq_pop(); // 一级队列pop出一个节点,实质就是服务对象的独立消息队列。
if (q==NULL)
return NULL;
}
uint32_t handle = skynet_mq_handle(q);
// 检查服务对象的正确性
struct skynet_context * ctx = skynet_handle_grab(handle);
if (ctx == NULL) {
struct drop_t d = { handle };
skynet_mq_release(q, drop_message, &d);
return skynet_globalmq_pop();
}
int i,n=1;
struct skynet_message msg;
for (i=0;i<n;i++) {
// 服务消息队列消息出列
if (skynet_mq_pop(q,&msg)) {
skynet_context_release(ctx);
return skynet_globalmq_pop();
} else if (i==0 && weight >= 0) {
n = skynet_mq_length(q);
n >>= weight;
}
// 消息队列过载警示
int overload = skynet_mq_overload(q);
if (overload) {
skynet_error(ctx, "May overload, message queue length = %d", overload);
}
// 监视器处理
skynet_monitor_trigger(sm, msg.source , handle);
if (ctx->cb == NULL) {
skynet_free(msg.data);
} else {
// 通知宿主上的lua处理事件
dispatch_message(ctx, &msg);
}
skynet_monitor_trigger(sm, 0,0);
}
assert(q == ctx->queue);
struct message_queue *nq = skynet_globalmq_pop();
if (nq) {
// If global mq is not empty , push q back, and return next queue (nq)
// Else (global mq is empty or block, don't push q back, and return q again (for next dispatch)
skynet_globalmq_push(q);
q = nq;
}
skynet_context_release(ctx);
return q;
}
static void *
thread_worker(void *p) {
struct worker_parm *wp = p;
int id = wp->id;
int weight = wp->weight;
struct monitor *m = wp->m;
struct skynet_monitor *sm = m->m[id];
skynet_initthread(THREAD_WORKER);
struct message_queue * q = NULL;
while (!m->quit) {
q = skynet_context_message_dispatch(sm, q, weight);
// 一级队列为空时,线程休眠,通过调节变量控制等待外部唤醒
if (q == NULL) {
if (pthread_mutex_lock(&m->mutex) == 0) {
++ m->sleep;
// "spurious wakeup" is harmless,
// because skynet_context_message_dispatch() can be call at any time.
if (!m->quit)
pthread_cond_wait(&m->cond, &m->mutex);
-- m->sleep;
if (pthread_mutex_unlock(&m->mutex)) {
fprintf(stderr, "unlock mutex error");
exit(1);
}
}
}
}
return NULL;
}
skynet从配置初始化,再到基本资源初始化,最终一步一步启动主要的模块。当然,这里只是简单的描述整个启动过程且尽量不涉及c层到lua层的控制权交接,这里是为了单纯的讲述c部分的内容,发散到lua的话涉及c与lua交互内容且还离不开这些基础模块的作用,所以先单纯描述c部分的工作,后续对lua层的逻辑描述才更清晰。