当前位置:首页 > 通信资讯 > 正文

linux定时器的使用(linux下定时器的实现方式分析)

linux定时器的使用(linux下定时器的实现方式分析)

定时器原理

一般定时器实现的方式有以下几种:

基于排序链表方式:

通过排序链表来保存定时器,由于链表是排序好的,所以获取最小(最早到期)的定时器的时间复杂度为 O(1)。但插入需要遍历整个链表,所以时间复杂度为 O(n)。如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

基于最小堆方式:

通过最小堆来保存定时器,在最小堆中获取最小定时器的时间复杂度为 O(1),但插入一个定时器的时间复杂度为 O(log n)。如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

基于平衡二叉树方式:

使用平衡二叉树(如红黑树)保存定时器,在平衡二叉树中获取最小定时器的时间复杂度为 O(log n)(也可以通过缓存最小值的方法来达到 O(1)),而插入一个定时器的时间复杂度为 O(log n)。如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

时间轮:

但对于Linux这种对定时器依赖性比较高(网络子模块的TCP协议使用了大量的定时器)的操作系统来说,以上的数据结构都是不能满足要求的。所以Linux使用了效率更高的定时器算法:时间轮。

时间轮 类似于日常生活的时钟,如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

日常生活的时钟,每当秒针转一圈时,分针就会走一格,而分针走一圈时,时针就会走一格。而时间轮的实现方式与时钟类似,就是把到期时间当成一个轮,然后把定时器挂在这个轮子上面,每当时间走一秒就移动时针,并且执行那个时针上的定时器,如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

一般的定时器范围为一个32位整型的大小,也就是 0 ~ 4294967295,如果通过一个数组来存储的话,就需要一个元素个数为4294967296的数组,非常浪费内存。这个时候就可以通过类似于时钟的方式:通过多级数组来存储。时钟通过时分秒来进行分级,当然我们也可以这样,但对于计算机来说,时分秒的分级不太友好,所以Linux内核中,对32位整型分为5个级别,第一个等级存储0 ~ 255秒 的定时器,第二个等级为 256秒 ~ 256*64秒,第三个等级为 256*64秒 ~ 256*64*64秒,第四个等级为 256*64*64秒 ~ 256*64*64*64秒,第五个等级为 256*64*64*64秒 ~ 256*64*64*64*64秒。如下图:

linux定时器的使用(linux下定时器的实现方式分析)

图片

注意:第二级至第五级数组的第一个槽是不挂任何定时器的。

每级数组上面都有一个指针,指向当前要执行的定时器。每当时间走一秒,Linux首先会移动第一级的指针,然后执行当前位置上的定时器。当指针变为0时,会移动下一级的指针,并把该位置上的定时器重新计算一次并且插入到时间轮中,其他级如此类推。如下图所示:

linux定时器的使用(linux下定时器的实现方式分析)

图片

当要执行到期的定时器只需要移动第一级数组上的指针并且执行该位置上的定时器列表即可,所以时间复杂度为 O(1),而插入一个定时器也很简单,先计算定时器的过期时间范围在哪一级数组上,并且连接到该位置上的链表即可,时间复杂度也是 O(1)。

Linux时间轮的实现

那么接下来我们看看Linux内核是怎么实现时间轮算法的。

定义五个等级的数组

  1. #defineTVN_BITS6
  2. #defineTVR_BITS8
  3. #defineTVN_SIZE(1<<TVN_BITS)//64
  4. #defineTVR_SIZE(1<<TVR_BITS)//256
  5. #defineTVN_MASK(TVN_SIZE-1)
  6. #defineTVR_MASK(TVR_SIZE-1)
  7. structtimer_vec{
  8. intindex;
  9. structlist_headvec[TVN_SIZE];
  10. };
  11. structtimer_vec_root{
  12. intindex;
  13. structlist_headvec[TVR_SIZE];
  14. };
  15. staticstructtimer_vectv5;
  16. staticstructtimer_vectv4;
  17. staticstructtimer_vectv3;
  18. staticstructtimer_vectv2;
  19. staticstructtimer_vec_roottv1;
  20. voidinit_timervecs(void)
  21. {
  22. inti;
  23. for(i=0;i<TVN_SIZE;i++){
  24. INIT_LIST_HEAD(tv5.vec+i);
  25. INIT_LIST_HEAD(tv4.vec+i);
  26. INIT_LIST_HEAD(tv3.vec+i);
  27. INIT_LIST_HEAD(tv2.vec+i);
  28. }
  29. for(i=0;i<TVR_SIZE;i++)
  30. INIT_LIST_HEAD(tv1.vec+i);
  31. }

上面的代码定义第一级数组为 timer_vec_root 类型,其 index 成员是当前要执行的定时器指针(对应 vec 成员的下标),而 vec 成员是一个链表数组,数组元素个数为256,每个元素上保存了该秒到期的定时器列表,其他等级的数组类似。

插入定时器

  1. staticinlinevoidinternal_add_timer(structtimer_list*timer)
  2. {
  3. /*
  4. *mustbecli-edwhencallingthis
  5. */
  6. unsignedlongexpires=timer->expires;
  7. unsignedlongidx=expires-timer_jiffies;
  8. structlist_head*vec;
  9. if(idx<TVR_SIZE){//0~255
  10. inti=expires&TVR_MASK;
  11. vec=tv1.vec+i;
  12. }elseif(idx<1<<(TVR_BITS+TVN_BITS)){//256~16191
  13. inti=(expires>>TVR_BITS)&TVN_MASK;
  14. vec=tv2.vec+i;
  15. }elseif(idx<1<<(TVR_BITS+2*TVN_BITS)){
  16. inti=(expires>>(TVR_BITS+TVN_BITS))&TVN_MASK;
  17. vec=tv3.vec+i;
  18. }elseif(idx<1<<(TVR_BITS+3*TVN_BITS)){
  19. inti=(expires>>(TVR_BITS+2*TVN_BITS))&TVN_MASK;
  20. vec=tv4.vec+i;
  21. }elseif((signedlong)idx<0){
  22. /*canhappenifyouaddatimerwithexpires==jiffies,
  23. *oryousetatimertogooffinthepast
  24. */
  25. vec=tv1.vec+tv1.index;
  26. }elseif(idx<=0xffffffffUL){
  27. inti=(expires>>(TVR_BITS+3*TVN_BITS))&TVN_MASK;
  28. vec=tv5.vec+i;
  29. }else{
  30. /*Canonlygethereonarchitectureswith64-bitjiffies*/
  31. INIT_LIST_HEAD(&timer->list);
  32. return;
  33. }
  34. /*
  35. *添加到链表中
  36. */
  37. list_add(&timer->list,vec->prev);
  38. }

internal_add_timer() 函数的主要工作是计算定时器到期时间所属的等级范围,然后把定时器添加到链表中。

执行到期的定时器

  1. staticinlinevoidcascade_timers(structtimer_vec*tv)
  2. {
  3. /*cascadeallthetimersfromtvuponelevel*/
  4. structlist_head*head,*curr,*next;
  5. head=tv->vec+tv->index;
  6. curr=head->next;
  7. /*
  8. *Weareremoving_all_timersfromthelist,sowedon'thaveto
  9. *detachthemindividually,justclearthelistafterwards.
  10. */
  11. while(curr!=head){
  12. structtimer_list*tmp;
  13. tmp=list_entry(curr,structtimer_list,list);
  14. next=curr->next;
  15. list_del(curr);
  16. internal_add_timer(tmp);
  17. curr=next;
  18. }
  19. INIT_LIST_HEAD(head);
  20. tv->index=(tv->index+1)&TVN_MASK;
  21. }
  22. staticinlinevoidrun_timer_list(void)
  23. {
  24. spin_lock_irq(&timerlist_lock);
  25. while((long)(jiffies-timer_jiffies)>=0){
  26. structlist_head*head,*curr;
  27. if(!tv1.index){//完成了一个轮回,移动下一个单位的定时器
  28. intn=1;
  29. do{
  30. cascade_timers(tvecs[n]);
  31. }while(tvecs[n]->index==1&&++n<NOOF_TVECS);
  32. }
  33. repeat:
  34. head=tv1.vec+tv1.index;
  35. curr=head->next;
  36. if(curr!=head){
  37. structtimer_list*timer;
  38. void(*fn)(unsignedlong);
  39. unsignedlongdata;
  40. timer=list_entry(curr,structtimer_list,list);
  41. fn=timer->function;
  42. data=timer->data;
  43. detach_timer(timer);
  44. timer->list.next=timer->list.prev=NULL;
  45. timer_enter(timer);
  46. spin_unlock_irq(&timerlist_lock);
  47. fn(data);
  48. spin_lock_irq(&timerlist_lock);
  49. timer_exit();
  50. gotorepeat;
  51. }
  52. ++timer_jiffies;
  53. tv1.index=(tv1.index+1)&TVR_MASK;
  54. }
  55. spin_unlock_irq(&timerlist_lock);
  56. }

执行到期的定时器主要通过 run_timer_list() 函数完成,该函数首先比较当前时间与最后一次运行 run_timer_list() 函数时间的差值,然后循环这个差值的次数,并执行当前指针位置上的定时器。每循环一次对第一级数组指针进行加一操作,当第一级数组指针变为0(即所有定时器都执行完),那么就移动下一个等级的指针,并把该位置上的定时器重新计算插入到时间轮中,重新计算定时器通过 cascade_timers() 函数实现。

原文链接:https://mp.weixin.qq.com/s/5MV_t6ns8sdDLJrWiuBayQ

如果您对该产品感兴趣,请填写办理(客服微信:xiaoxiongyidong)

为您推荐:

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。