1.8. Skynet定时器原理
定时器涉及到的结构体为:
struct timer_event { //记录每个节点回复消息的信息,存储在节点的后面
uint32_t handle; //记录定位服务的编号
int session; //记录用于接收消息响应时,定位到是响应哪一条消息,由发送消息的服务生成
};
struct timer_node { //节点
struct timer_node *next; //指向下一个节点
uint32_t expire; //保存该节点的timer_event消息回复事件的触发时间片为:添加时的时间片加上延时触发的时间
};
struct link_list { //链表
struct timer_node head; //头节点,head.next指向第一个节点
struct timer_node *tail; //尾节点
};
struct timer { //定时器的信息存储结构
struct link_list near[TIME_NEAR]; //保存时间片低8位的链表,每次都是从该数组中取链表
struct link_list t[4][TIME_LEVEL]; //保存时间片高24位的链表,按照0,1,2,3从低位到高位都分别对应6位
struct spinlock lock; //锁
uint32_t time; //当前的时间片,单位为1/100秒
uint32_t starttime; //系统的开始实时时间,从UTC1970-1-1 0:0:0开始计时,精确到秒
uint64_t current; //从开始时刻到现在的时长,精确到1/100秒
uint64_t current_point; //系统启动时长,精确到1/100秒
};
定时器的原理为:如上图所示,32为位无符号整数time记录时间片分别对应数组near[256]和t[4][64],每次添加节点时(skynet_timer.c文件中的add_node函数):(简单的说:如果expire与time之差小于256则将节点添加到near数组对应元素的链表中,否则从高位往低位依次比较expire的第i个6位二进制的值n与time的第i个6位二进制的值m,哪个不相等则将节点添加到数组t[4-i][n]对应的元素链表中),而下面是从低位往高位进行比较的。
- 首先检查节点的expire与time的高24位是否相等,相等则将该节点添加到expire低8位值对应数组near的元素的链表中,不相等则进行下一步。
- 检查expire与time的高18位是否相等,相等则将该节点添加到expire低第9位到第14位对应的6位二进制值对应数组t[0]的元素的链表中,如果不相等则进行下一步。
- 检查expire与time的高12位是否相等,相等则将该节点添加到expire低第15位到第20位对应的6位二进制值对应数组t[1]的元素的链表中,如果不相等则进行下一步。
- 检查expire与time的高6位是否相等,相等则将该节点添加到expire低第21位到第26位对应的6位二进制值对应数组t[2]的元素的链表中,如果不相等则进行下一步。
- 将该节点添加到expire低第27位到第32位对应的6位二进制值对应数组t[3]的元素的链表中
定时器线程每隔2500微秒会调用skynet_timer.c文件中的skynet_updatetime函数刷新时间,函数skynet_updatetime的原理为:将本次执行函数skynet_updatetime到上一次执行函数skynet_updatetime的这段时间间隔划分为以1/100秒为单位的时间片,并对这些时间片中的每个时间片都依次进行取操作(skynet_timer.c文件中的timer_execute函数)、刷新时间片time、移动t[4][64]中的链表操作以及取操作。取操作(timer_execute函数)的原理是将time的低8位值对应的near[256]数组中的链表取出,依次对链表中的所有节点进行消息回复。刷新时间片time、移动t[4][64]中的链表操作(timer_shift函数)的原理是刷新时间片time:
- 检查time是否溢出,如果溢出则将t[3][0]这个链表取出并依次将该链表中的节点添加(即实现该链表的移动操作),如果time未溢出,则进行下一步。
- 检查time低8位是否溢出产生进位,没有则结束,有则检查time的低第9位到第14位是否产生溢出,没有则将time的低第9位到第14位对应的值对应数组t[0]中的链表取出,并依次将该链表中的节点添加(即实现该链表的移动操作),如果有溢出,则进行下一步。
- 检查time低14位是否溢出产生进位,没有则结束,有则检查time的低第15位到第20位是否产生溢出,没有则将time的低第15位到第20位对应的值对应数组t[1]中的链表取出,并依次将该链表中的节点添加(即实现该链表的移动操作),如果有溢出,则进行下一步。
- 检查time低20位是否溢出产生进位,没有则结束,有则检查time的低第21位到第26位是否产生溢出,没有则将time的低第21位到第26位对应的值对应数组t[2]中的链表取出,并依次将该链表中的节点添加(即实现该链表的移动操作),如果有溢出,则进行下一步。
- 检查time低26位是否溢出产生进位,没有则结束,有则检查time的低第27位到第32位是否产生溢出,没有则将time的低第27位到第32位对应的值对应数组t[3]中的链表取出,并依次将该链表中的节点添加(即实现该链表的移动操作)。
在skynet_start.c文件这的skynet_start函数调用了skynet_timer.c文件中的skynet_timer_init函数进行定时器初始化
//初始化系统计时
void skynet_timer_init(void) {
TI = timer_create_timer(); //新建一个计时信息结构体
uint32_t current = 0;
systime(&TI->starttime, ¤t); //获取系统初始化时的UTC时间
TI->current = current;
TI->current_point = gettime(); //获得系统启动时的CPU时间
}
//创建一个计时信息结构体
static struct timer * timer_create_timer() {
struct timer *r=(struct timer *)skynet_malloc(sizeof(struct timer));
memset(r,0,sizeof(*r));
int i,j;
for (i=0;i<TIME_NEAR;i++) {
link_clear(&r->near[i]); //清空链表
}
for (i=0;i<4;i++) {
for (j=0;j<TIME_LEVEL;j++) {
link_clear(&r->t[i][j]); //清空链表
}
}
SPIN_INIT(r)
r->current = 0;
return r;
}
//清除指定链表,并返回指向链表的第一个节点的指针
static inline struct timer_node * link_clear(struct link_list *list) {
struct timer_node * ret = list->head.next; //取出指向第一个节点的指针
list->head.next = 0;
list->tail = &(list->head);
return ret;
}
添加定时回复节点:
//定时回复,time的单位为1/100秒
int skynet_timeout(uint32_t handle, int time, int session) {
if (time <= 0) { //如果时间小于或等于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);
}
return session;
}
//向链表中添加节点,time的单位为1/100秒
static void timer_add(struct timer *T,void *arg,size_t sz,int time) {
//分配一个节点timer_node和附加的timer_event的内存块
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);
}
//添加节点,将节点触发的时间和当前时间相比小于256的节点添加到near数组中,
//依次越靠近当前时间的添加到越低位
static void add_node(struct timer *T,struct timer_node *node) {
uint32_t time=node->expire; //回复的时间片
uint32_t current_time=T->time;
//TIME_NEAR_MASK=0xff,如果高24位相等则将该节点添加到底8位中对应的链表
if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {
link(&T->near[time&TIME_NEAR_MASK],node); //将该节点加入低8位对应的链表
} else {
int i;
uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT; //TIME_NEAR=256,TIME_LEVEL_SHIFT=6初始化为低14位的掩码
for (i=0;i<3;i++) {
if ((time|(mask-1))==(current_time|(mask-1))) {
break;
}
mask <<= TIME_LEVEL_SHIFT; //往左移6位,即依次产生20位,26位,32位掩码
}
//将该节点加入高24位对应的链表 TIME_NEAR_SHIFT=8,TIME_LEVEL_MASK=0x3f
link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
}
}
//向link_list链表中添加timer_node,head->next指向第一个,
//tail指向尾节点,并且节点timer_node的后面附加有timer_event的信息
static inline void link(struct link_list *list,struct timer_node *node) {
list->tail->next = node;
list->tail = node;
node->next=0;
}
定时器线程刷新时间:
//刷新时间
void skynet_updatetime(void) {
uint64_t cp = gettime(); //获得从系统启动开始计时的时间,不受系统时间被用户改变的影响,精确到1/100秒
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); //从系统启动到目前的时间差,精确到1/100秒
TI->current_point = cp; //记录下当前的时间,不受系统时间被用户改变的影响,精确到1/100秒
TI->current += diff;
int i;
for (i=0;i<diff;i++) {
timer_update(TI); //刷新时间片
}
}
}
//刷新时间片
static void timer_update(struct timer *T) {
SPIN_LOCK(T);
// try to dispatch timeout 0 (rare condition)
timer_execute(T); //检查当前的时间片的低8位对应的数组元素的链表是否为空,不为空则取出
// shift time first, and then dispatch timer message
timer_shift(T); //时间片time自加1,移动高24位的链表
timer_execute(T); //检查当前的时间片的低8位对应的数组元素的链表是否为空,不为空则取出
SPIN_UNLOCK(T);
}
//检查当前的时间片的低8位对应的数组元素的链表是否为空,不为空则取出
static inline void timer_execute(struct timer *T) {
int idx = T->time & TIME_NEAR_MASK; //取低8位对应的值
while (T->near[idx].head.next) { //如果低8位值对应的数组元素有链表,则取出
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);
}
}
//将高24位对应的4个6位的数组中的各个元素的链表往低位移
static void timer_shift(struct timer *T) {
int mask = TIME_NEAR; //256掩码
uint32_t ct = ++T->time; //时间片自加1
if (ct == 0) { //如果时间片已经溢出即归0
move_list(T, 3, 0); //将对应的t[3][0]链表取出,然后再依次将链表的节点添加,即实现了移动操作
} else {
uint32_t time = ct >> TIME_NEAR_SHIFT; //ct右移8位
int i=0;
while ((ct & (mask-1))==0) { //开始时,如果低8位溢出产生进位
int idx=time & TIME_LEVEL_MASK; //time & 0x3f 即取T->time的9-14位的值
if (idx!=0) {
move_list(T, i, idx); //将对应的t[i][idx]链表取出,然后再依次将链表的节点添加,即实现了移动操作
break;
}
mask <<= TIME_LEVEL_SHIFT; //mask左移6位
time >>= TIME_LEVEL_SHIFT; //time右移6位
++i;
}
}
}