Title:Implementing Software Timers

By:Don Libes 

翻译:CoreyGao   

  

   这篇文章提供了实现软件计时器所需的一系列函数。软件计时器是什么?你为什么需要实现软件计时器?软件计时器弥补了硬件计时器的先天不足。比如,对于大部分电脑的硬件计时器,你只能让时钟在未来某个指定时间触发一次中断(译者注:例如UNIX下的alarm函数)。

  当运行多任务时,你就会想要让时钟能够同步地追踪多个计时器。即使多个计时器的时间相互重叠,时钟也必须能够正确的产生中断。操作系统始终都在重复上述过程。

  Robet Ward在1990年四月的《C User's Journal》上发表了“Practical Schedulers for Real-Time Application”一文。文章研究了一个与本文主题相关的问题--建立一个通用调度器。在那篇文章的"Addition Ideas"一节中,Robert描述了计时器调度队列的作用。“通过在一个指定的队列里插入计时器请求,一个事件就可以指定其他事件的运行时间”。(Events can specify the timing of other events by putting a timer programming      request in a special queue).这正是本篇文章内的程序要做的事情。

  这些代码也可以做其他事情,比如,在UNIX环境下,每个进程只能有一个计时器,但是用这个程序,你可以设置任意数量的计时器。即使你对计时器不敢兴趣,你也会这篇文章很有趣。通过一些简单的技巧和数据结构,这些C代码可以产生巨大的效果。这些代码很有技巧性。而且如果你阅读并且写过足够多的C代码的话,你会觉得我的注释也很有趣。

计时器

  通过组合多个模块来实现计时器,我们可以降低复杂性。有些人喜欢模块化,有些人讨厌。相似的是,有些操作系统是模块化的,而有些操作系统却不是。我喜欢模块化,模块化可以让代码变得容易编写,容易阅读,而且容易调试。

  计时器的基本理念就是允许任务在将来的某个时间点运行。当时间点到来时,他们被调度器调度运行。实际运行这些任务的是调度器。为了能够与调度器通信,我们定义了一个叫做timer的数据结构(list 1)。我还在list 1中包括了一些其他必须的定义。例如,TIME是用来代表时间变量的。你可以根据自己的需求实现这些定义。

listing 1
 1 #include <stdio.h>
 2 
 3 #define TRUE    1
 4 #define FALSE    0
 5 
 6 #define MAX_TIMERS    ...    /* number of timers */
 7 typedef ... TIME;        /* how time is actually stored */
 8 #define VERY_LONG_TIME    ...    /* longest time possible */
 9 
10 struct timer {
11     int inuse;        /* TRUE if in use */
12     TIME time;        /* relative time to wait */
13     char *event;        /* set to TRUE at timeout */
14 } timers[MAX_TIMERS];        /* set of timers */

 

  每一个timer结构代表一个计时器。计时器集合通过一个timer的数组实现。timer结构的第一个元素代表当前计时器是否被使用。第二个元素代表当前计时器需要等待的时间。*event的初始值是0。当到达指定时间时,*event的值被设为1。调度器同时也关注event指针。当*event == 1时,调度器知道与此计时器相关的任务必须启动。

   list 2中的代码初始化timers数组。

listing 2
1 void timers_init() {
2     struct timer *t;
3 
4     for (t=timers;t<&timers[MAX_TIMERS];t++)
5         t->inuse = FALSE;
6 }

 


  现在我们可以开始写调度timers的程序了。首先是timer_undeclare。与相对应的timer_declare相比,timer_undeclare简单一点。

  有相当多的方式可以跟踪计时器。没有硬件时钟的电脑通过CPU的时钟周期产生中断来计时。软件通过一个寄存器来维护系统时间和检查计时器是否超时。

  现代的电脑都有硬件时钟。只有在给定时间到来时才会中断CPU。通过在给定时间产生中断,系统速度大幅度提升。在“虚拟软件”和“线程的实现”中 都大量有运用了这种技术。

  读取时间需要使用“系统调用”。但是受限于本文的主题,我们假设time_now变量自动读取当前时间。volatile关键字说明 这个变量不能在寄存器中缓存,只能从内存中读取.

volatile TIME time_now;

 

  为了方便我们定义了如下变量:timer_next指向下一个将要触发的定时器。time_timer_set保存上一次读取的时间。

Listing 3
 1 struct timer *timer_next = NULL;/* timer we expect to run down next */
 2 TIME time_timer_set;        /* time when physical timer was set */
 3 
 4 void timers_update();        /* see discussion below */
 5 
 6 void  timer_undeclare(struct timer *t )
 7 {
 8     disable_interrupts();
 9     if (!t->inuse) {
10         enable_interrupts();
11         return;
12     }
13 
14     t->inuse = FALSE;
15 
16     /* check if we were waiting on this one */
17     if (t == timer_next) {
18         timers_update(time_now - time_timer_set);
19         if (timer_next) {
20             start_physical_timer(timer_next->time);
21             time_timer_set = time_now;
22         }
23     }
24     enable_interrupts();
25 }

 

取消计时器 - Why and How?

  timer_undeclare的名字表明了它的作用-取消计时器。在某些应用中,取消计时器是非常重要的操作。比如,网络通信的代码疯狂的使用计时器。在某些协议中,每发送一个packet就会产生一个计时器。如果发送方在给定的间隔内没收到应答,计时器就会触发相同packet的再次发送。如果发送方收到了应答,发送方就会取消与此packet对应的计时器。如果一切顺利的话,每个计时器都会在一段时间后被取消。

  timer_undeclare(list 3)在执行之前首先禁用中断。因为中断处理程序与本程序使用相同的数据。因为数据是共享的,所以必须严格控制权限。disable_interrupts()函数内容是依赖系统实现的。各位读者自行实现。timer_undeclare函数首先检查计时器是否处于使用状态。在稍后的内容中我们可以看到,操作系统可以暗中改变计时器状态。因此我们必须首先检查计时器状态。如果计时器是使用中的,timer_undeclare函数使计时器不可用。但是如果参数所指定的计时器恰巧是下一个将要触发的计时器,那么physical timer必须重新设置为剩余的所有计时器中最短的一个的时间。在这之前,所有的计时器必须更新为当前时间减去上一次设置的时间。timers_update用于完成这个任务,同时也计算了剩余所有时间中最短的那个。

  timers_update (listing 4)遍历了timers数组,将每个计时器的剩余时间减去了指定的时间。如果当前计时器的时间小于给定时间,那么这个计时器就会触发。在创建计时器和调用timers_update之间的延迟 与 通过系统调用获得当前时间的延迟 相互抵消。最后,我们在timer_next中记录下一个最短的计时器。

  timer_last是临时变量。当所有计时器都被调度完以后,timer_last才会出现,而且永远不会被调度。

listing 4
 1 /* subtract time from all timers, enabling any that run out along the way */
 2 void
 3 timers_update(time)
 4 TIME time;
 5 {
 6     static struct timer timer_last = {
 7         FALSE            /* in use */,
 8         VERY_LONG_TIME        /* time */,
 9         NULL            /* event pointer */
10     };
11 
12     struct timer *t;
13 
14     timer_next = &timer_last;
15 
16     for (t=timers;t<&timers[MAX_TIMERS];t++) {
17         if (t->inuse) {
18             if (time < t->time) { /* unexpired */
19                 t->time -= time;
20                 if (t->time < timer_next->time)
21                     timer_next = t;
22             } else { /* expired */
23                 /* tell scheduler */
24                 *t->event = TRUE;
25                 t->inuse = 0;    /* remove timer */
26             }
27         }
28     }
29 
30     /* reset timer_next if no timers found */
31     if (!timer_next->inuse) timer_next = 0;
32 }

 


声明计时器

  timer_declare(list 5)函数接受时间和一个event的地址作为参数。当指定时间到达时,*event被设置为1(在timers_update函数中/* tell scheduler*/下面)。timer_declare函数返回一个指向struct timer的指针。这个指针与timer_undeclare函数的参数是同一个。

listing 5
 1 struct timer *
 2 timer_declare(time,event)
 3 unsigned int time;        /* time to wait in 10msec ticks */
 4 char *event;
 5 {
 6     struct timer *t;
 7 
 8     disable_interrupts();
 9 
10     for (t=timers;t<&timers[MAX_TIMERS];t++) {
11         if (!t->inuse) break;
12     }
13 
14     /* out of timers? */
15     if (t == &timers[MAX_TIMERS]) {
16         enable_interrupts();
17         return(0);
18     }
19 
20     /* install new timer */
21     t->event = event;
22     t->time = time;
23     if (!timer_next) {
24         /* no timers set at all, so this is shortest */
25         time_timer_set = time_now;
26         start_physical_timer((timer_next = t)->time);
27     } else if ((time + time_now) < (timer_next->time + time_timer_set)) {
28         /* new timer is shorter than current one, so */
29         timers_update(time_now - time_timer_set);
30         time_timer_set = time_now;
31         start_physical_timer((timer_next = t)->time);
32     } else {
33         /* new timer is longer, than current one */
34     }
35     t->inuse = TRUE;
36     enable_interrupts();
37     return(t);
38 }

 

  与它相对应的函数一样,timer_declare中断被禁用,以防产生竞争。

  timer_declare首先为定时器分配空间。如果所有定时器被用完,返回NULL。

  一旦定时器被初始化,我们必须检查physical timer有没有被改变。有三种情况:

  1)没有其他定时器:

    在这种情况下,我们将physical timer设置为当前定时器的时间。

  2)有其他定时器,但是新声明的定时器是最短的:

    在这种情况下,我们必须取消以前的physical timer,并将physical timer的值设置为新声明的定时器的值。但是在此之前,我们必须更新其他所有定时器的时间。

  3)有其他定时器,而且新声明的定时器不是最短的:

    在这种情况下我们什么也不用做。为了容易理解,在else{}中添加了注释。

  在启用中断并返回之前,当前计时器的inuse变量设置为TRUE。在最后设置inuse,是为了防止timers_update错误地将当前定时器更新。

处理计时器中断

   剩下的最后一个任务。计时器超时以后调用的interrupt handler。当interrrupt handler被调用以后,我们认为timer_next已经超时。

listing 6
 1 void
 2 timer_interrupt_handler() {
 3     timers_update(time_now - time_timer_set);
 4     
 5     /* start physical timer for next shortest time if one exists */
 6     if (timer_next) {
 7         time_timer_set = time_now;
 8         start_physical_timer(timer_next->time);
 9     }
10 }

 

   每当interrupt handler被调用时,就有一个计时器超时。
  每当timers_update被调用时,所有的inuse状态的计时器都被更新,所有的超时计时器的*event都被设置为1.如果条件满足,timer_next也会被更新为新值。physical timer被重新设置.

  让我们通过一个特殊情况来检查一下这个程序假设我们只设置了一个计时器。现在我们调用timer_undelcare。中断被禁用,physical timer照常运行。当中断被启用时,中断会被立刻传送给进程,但是此时计时器已被取消。所以现在的情况是,计时器被取消,但是中断被传送给了进程。在interrupt handler中会发生什么呢?timers_update被调用。timers_update没有发现任何可供更新的计时器。然后timer_next被设置为0.interrupt handler函数的剩余部分很好的处理了这种情况( if(timer_next) )。

  这是上述程序必须处理的一种特殊情况(事实上,在我写的第一个版本中并没有处理这种情况。随后的debug让我十分蛋疼。涉及到中断时,debugger并不好用)。

结论

   通过这篇文章我实现了一个软件计时器。通过仔细的设计,这个计时器实现了与调度器的分离。例如,当你同时使用另一种计时器时,这篇文章的计时器不需要关闭调度器。

  当你阅读这篇文章时你可能想,为什么不用链表而是用数组来维护计时器。使用链表可以避免溢出。使链表保持排序状态 可以让timers_update的实现更加简单。

  但是从另一方面看这个问题,使用链表会让其余的函数变得更复杂。比如,要实现timer_undeclare,就会要求你要么实现一个双向链表,要么从头到尾的遍历整个链表。另外,real-time system通常避免使用动态数据结构。例如,使用进程空间的堆进程malloc/free会产生很难估计的时间消耗。如果我用链表重新实现这个程序,我会自己实现一个malloc函数。该malloc从我自己设定的内存池中分配空间。这种方式 事实上与使用和肃卒没有区别。在time和space上,我们必须进行取舍。

  如果你决定重新编写或者修改一下这些代码,一定要自信。要牢记两个进程同时操作相同数据结构的后果。

  Happy interruptions!

  Thanks

posted on 2013-05-01 17:54  CoreyGao  阅读(1177)  评论(0编辑  收藏  举报