Skynet source code analysis of the timer skynet_timer.c

Skynet has its own timer function skynet-src/skynet_timer.c, when skynet starts, a thread will be created to run the timer. Call skynet_updatetime() every frame (0.0025 seconds/frame)

 1 // skynet-src/skynet_start.c
 2 
 3 create_thread(&pid[1], thread_timer, m);
 4 
 5 static void *
 6 thread_timer(void *p) {
 7     struct monitor * m = p;
 8     skynet_initthread(THREAD_TIMER);
 9     for (;;) {
10         skynet_updatetime();
11         CHECK_ABORT
12         wakeup(m,m->count-1);
13         usleep(2500);  //2500微妙=0.0025秒
14         if (SIG) {
15             signal_hup();
16             SIG = 0;
17         }
18     }
19     ...
20 }

1. Design Ideas

The design idea of ​​skynet refers to the mechanism of Linux kernel dynamic timer, and refers to the introduction of Linux dynamic kernel timer http://www.cnblogs.com/leaven/archive/2010/08/19/1803382.html,

 

In skynet, the time accuracy is 0.01 seconds, which is enough for the game server. The definition is 1 tick = 0.01 second, and 1 second = 100 ticks. The core idea is: each timer sets an expired number of ticks, and the number of ticks of the current system (0 at startup, then 1 tick 1 tick and then jump back) to compare the difference, if the difference interval is relatively small (0 <=interval<=2^8-1), which means that the timer is coming and needs to be closely followed. Save them in the 2^8 timer linked list; if the interval is larger, the timer is farther away, so you don’t need to pay too much attention. , Divided into 4 levels, 2^8<=interval<=2^(8+6)-1, 2^(8+6)<=interval<=2^(8+6+6),... , Each level only needs 2^6 timer linked lists to save. For example, for a timer with 2^8<=interval<=2^(8+6)-1, save the same value idx of interval>>8 in the first In a linked list whose rank position is idx.

The advantage of this is that instead of creating a linked list for each interval, only 2^8+4*(2^6) linked lists are required, which greatly saves memory.

After that, in different situations, different levels of timers are allocated. The higher the level, the more distant it is, and the fewer times it needs to be re-allocated.

For friends who are engaged in game client development, the interface development is very cumbersome, the client technology is updated very quickly, and seldom can contact the underlying development, friends who want to transform to be game server development, learn skynet to understand the overall architecture process and basic components of the server And the programming thinking of the server.

For small game development, skynet is a lightweight game server framework, while learning the server side, full stack development

About Skynet, here is a learning video for everyone, which explains the following knowledge points in detail, the video link: https://ke.qq.com/course/2806743?flowToken=1030833

 

2. Source code analysis

Data structure: timer->near, save 2^8 upcoming timer linked lists; timer->t, save 4 hierarchical arrays, each item of the array is a linked list; timer->time saves from skynet start to now Number of ticks

 1 // skynet-src/skynet_timer.c
 2 struct timer_event {
 3     uint32_t handle;
 4     int session;
 5 };
 6 
 7 struct timer_node { //单个定时器节点
 8     struct timer_node *next;
 9     uint32_t expire; //到期滴答数
10 };
11 
12 struct link_list { //定时器链表
13     struct timer_node head;
14     struct timer_node *tail;
15 };
16 
17 struct timer {
18     struct link_list near[TIME_NEAR];
19     struct link_list t[4][TIME_LEVEL]; 
20     struct spinlock lock;
21     uint32_t time; //启动到现在走过的滴答数,等同于current
22     ...
23 };

Call skynet_timeout to create a timer

Then call timer_add (line 8) to allocate space for the timer_node pointer. There is no timer_event field in the timer_node structure. In addition to the size of node itself, an additional space of timer_event size is allocated to store the event, and then the location of node+1 can be used Get timer_event data

Then call add_node (line 21) and add it to the timer list. If the timer's expired tick count is close to the current one (<2^8), it means that the trigger timer will be added to the T->near array, otherwise Add to the corresponding T->T[i] according to the difference

 1 // skynet-src/skynet_timer.c
 2 int
 3 skynet_timeout(uint32_t handle, int time, int session) {
 4     ...
 5     struct timer_event event;
 6     event.handle = handle;
 7     event.session = session;
 8     timer_add(TI, &event, sizeof(event), time);
 9 
10     return session;
11 }
12 
13 static void
14 timer_add(struct timer *T,void *arg,size_t sz,int time) {
15     struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
16     memcpy(node+1,arg,sz);
17 
18     SPIN_LOCK(T);
19 
20     node->expire=time+T->time;
21     add_node(T,node);
22 
23     SPIN_UNLOCK(T);
24 }
25 
26 static void
27 add_node(struct timer *T,struct timer_node *node) {
28     uint32_t time=node->expire;
29     uint32_t current_time=T->time;
30         
31     if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {
32         link(&T->near[time&TIME_NEAR_MASK],node);
33     } else {
34         int i;
35         uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
36         for (i=0;i<3;i++) {
37             if ((time|(mask-1))==(current_time|(mask-1))) {
38                  break;
39             }
40             mask <<= TIME_LEVEL_SHIFT;
41         }
42 
43         link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);        
44     }
45 }

Each frame triggers the expired timer linked list from T->near, and the number of expired ticks for all nodes in the linked list of each item in the near array is the same.

Call dispatch_list to distribute, get timer_event data through current+1 (line 18), and then push a message to event->handle to trigger the timer (line 25)

 1 // skynet-src/skynet_timer.c
 2 static inline void
 3 timer_execute(struct timer *T) {
 4     int idx = T->time & TIME_NEAR_MASK;
 5         
 6     while (T->near[idx].head.next) {
 7         struct timer_node *current = link_clear(&T->near[idx]);
 8         SPIN_UNLOCK(T);
 9         // dispatch_list don't need lock T
10         dispatch_list(current);
11         SPIN_LOCK(T);
12     }
13 }
14 
15 static inline void
16 dispatch_list(struct timer_node *current) {
17     do {
18         struct timer_event * event = (struct timer_event *)(current+1);
19         struct skynet_message message;
20         message.source = 0;
21         message.session = event->session;
22         message.data = NULL;
23         message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
24 
25        skynet_context_push(event->handle, &message);
26                
27        struct timer_node * temp = current;
28        current=current->next;
29        skynet_free(temp);      
30     } while (current);
31 }

In addition to triggering the timer in each frame, it is also necessary to re-allocate the interval (timer_shift) where the timer is located. Because the timer to be triggered is stored in T->near, there is only a number of ticks per TIME_NEAR-1 (2^8-1) May need to be allocated (line 22). Otherwise, assign a certain level in T->t.

When the 8th bit of T->time is not all 0, there is no need to allocate, so every 2^8 ticks need to be allocated once;

When the 9th to 14th bits of T->time are not all 0, re-allocate the T[0] level, and allocate once every 2^8 ticks, idx starts from 1, and +1 is allocated each time;

When the 15th-20th digits of T->time are not all 0, re-allocate T[1] level, once every 2^(8+6) ticks, idx starts from 1, and +1 is assigned every time;

When the 21st-26th bits of T->time are not all 0, the T[2] level is re-allocated, every 2^(8+6+6) ticks are allocated once, idx starts from 1, and +1 is allocated each time ;

When the 27th-32th bits of T->time are not all 0, the T[3] level is re-allocated, and every 2^(8+6+6+6) ticks are allocated once, idx starts from 1, and each allocation +1;

That is, the higher the level of the timer, the farther away, the less concerned it is, and the fewer times it needs to be reassigned.

 1 // skynet-src/skynet_timer.c
 2 static void
 3 move_list(struct timer *T, int level, int idx) {
 4     struct timer_node *current = link_clear(&T->t[level][idx]);
 5     while (current) {
 6         struct timer_node *temp=current->next;
 7         add_node(T,current);
 8         current=temp;
 9     }
10 }
11 
12 static void
13 timer_shift(struct timer *T) {
14     int mask = TIME_NEAR;
15     uint32_t ct = ++T->time;
16     if (ct == 0) {
17         move_list(T, 3, 0);
18     } else {
19         uint32_t time = ct >> TIME_NEAR_SHIFT;
20         int i=0;
21 
22         while ((ct & (mask-1))==0) {
23             int idx=time & TIME_LEVEL_MASK;
24             if (idx!=0) {
25                 move_list(T, i, idx);
26                 break;                          
27             }
28             mask <<= TIME_LEVEL_SHIFT;
29             time >>= TIME_LEVEL_SHIFT;
30             ++i;
31         }
32     }
33 }

3. How to use

Create a timer in the C layer through skynet_timeout

Create a timer through skynet.timeout in the Lua layer, for example, skynet.timeout(200, f), which triggers the callback function f after 200 ticks (2 seconds).

 

 

Guess you like

Origin blog.csdn.net/Linuxhus/article/details/112032698