6 使用“哨兵”简化程序(harib10i)

标题中略显突兀的“哨兵”一词,其实是程序技巧中的专门用语。一般来讲,说到“哨兵”,大家会想到巡逻的士兵。可一旦大家知道了这项技术的内容,肯定会认为这个名字取得太好了。总之笔者认为这是一个很棒的名称。

顺便说一下,上一节的技术称为“线性表”(linear list)。可笔者觉得这个名字不太容易理解。其实它就像挖红薯一样,我们拔第一根红薯蔓,就能挖出第一个红薯,再拔这个红薯的蔓,就能挖出下一个红薯来,所以,如果是笔者的话,就会把这种技术命名为“拔红薯式”。

■■■■■

harib10h的timer.c程序在去除移位处理后,其中的timer_settime函数还是有些冗长。浓缩的才是精华,我们来想办法简化这个程序。

我们看看程序就会发现,其中有4种可能:

  • 运行中的定时器只有一个的情况

  • 插入到最前面的情况

  • 插入到s和t之间的情况

  • 插入到最后面的情况

“既然有这么多种情况,当然要加各种条件,程序长了点也没办法”,大家可不能这样轻易放弃。如果是因为有4种可能才使程序变复杂了的话,那我们想办法消除这4种可能性不就行了吗?

我们来看看具体的做法。在进行初始化的时候,将时刻0xffffffff的定时器连到最后一个定时器上。虽然我们偷了点懒没有设定fifo等,但不必担心。反正无论如何都不可能到达这个时刻(在到达之前会修改时刻),所以不可能发生超时问题。它一直处于后面,只是个附带物,是个留下来看家的留守者。这个留守者正是“哨兵“。

加入了哨兵

  1. void init_pit(void)
  2. {
  3. int i;
  4. struct TIMER *t;
  5. io_out8(PIT_CTRL, 0x34);
  6. io_out8(PIT_CNT0, 0x9c);
  7. io_out8(PIT_CNT0, 0x2e);
  8. timerctl.count = 0;
  9. for (i = 0; i < MAX_TIMER; i++) {
  10. timerctl.timers0[i].flags = 0; /* 没有使用 */
  11. }
  12. t = timer_alloc(); /* 取得一个 */
  13. t->timeout = 0xffffffff;
  14. t->flags = TIMER_FLAGS_USING;
  15. t->next = 0; /* 末尾 */
  16. timerctl.t0 = t; /* 因为现在只有哨兵,所以他就在最前面*/
  17. timerctl.next = 0xffffffff; /* 因为只有哨兵,所以下一个超时时刻就是哨兵的时刻 */
  18. timerctl.using = 1;
  19. return;
  20. }

如果使用在12.5节中介绍的一年调整一次时刻的程序,就必须修改程序来保证不改变哨兵的时刻。比如改写成下面这样:

哨兵的时刻调整程序

  1. int t0 = timerctl.count; /* 所有的时刻都要减去这个值 */
  2. io_cli(); /* 在时刻调整时禁止中断 */
  3. timerctl.count -= t0;
  4. for (i = 0; i < MAX_TIMER; i++) {
  5. if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
  6. if (timerctl.timer[i].timeout != 0xffffffff) {
  7. timerctl.timer[i].timeout -= t0;
  8. }
  9. }
  10. }
  11. io_sti();

■■■■■

由于加入了哨兵,settime的状况就变了。4种情况中有2种情况是绝对不会发生的。(下面的第1种和第4种)

  • 处于运行中的定时器只有这1个的情况(因为有哨兵,所以最少应该有2个)

  • 插入最前面的情况

  • 插入s和t之间的情况

  • 插入最后的情况(哨兵总是在最后)

所以程序能简化不少,缩短了16行。

精简后的timer_settime

  1. void timer_settime(struct TIMER *timer, unsigned int timeout)
  2. {
  3. int e;
  4. struct TIMER *t, *s;
  5. timer->timeout = timeout + timerctl.count;
  6. timer->flags = TIMER_FLAGS_USING;
  7. e = io_load_eflags();
  8. io_cli();
  9. timerctl.using++;
  10. t = timerctl.t0;
  11. if (timer->timeout <= t->timeout) {
  12. /* 插入最前面的情况 */
  13. timerctl.t0 = timer;
  14. timer->next = t; /* 下面是设定t */
  15. timerctl.next = timer->timeout;
  16. io_store_eflags(e);
  17. return;
  18. }
  19. /* 搜寻插入位置 */
  20. for (;;) {
  21. s = t;
  22. t = t->next;
  23. if (timer->timeout <= t->timeout) {
  24. /* 插入s和t之间的情况 */
  25. s->next = timer; /* s下一个是timer */
  26. timer->next = t; /* timer的下一个是t */
  27. io_store_eflags(e);
  28. return;
  29. }
  30. }
  31. }

■■■■■

现在我们终于能简化inthandler20函数了。程序最后的部分。

稍稍精简了的inthandler20

  1. void inthandler20(int *esp)
  2. {
  3. int i;
  4. struct TIMER *timer;
  5. io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
  6. timerctl.count++;
  7. if (timerctl.next > timerctl.count) {
  8. return;
  9. }
  10. timer = timerctl.t0; /* 首先把最前面的地址赋给timer */
  11. for (i = 0; i < timerctl.using; i++) {
  12. /* 因为timers的定时器都处于运行状态,所以不确认flags */
  13. if (timer->timeout > timerctl.count) {
  14. break;
  15. }
  16. /* 超时 */
  17. timer->flags = TIMER_FLAGS_ALLOC;
  18. fifo32_put(timer->fifo, timer->data);
  19. timer = timer->next; /* 将下一定时器的地址代入timer*/
  20. }
  21. timerctl.using -= i;
  22. /* 新移位 */
  23. timerctl.t0 = timer;
  24. /* timerctl.next的设定 */ /* 这里! */
  25. timerctl.next = timerctl.t0->timeout;
  26. return;
  27. }

■■■■■

修改到这里以后,using就没什么用处了。……using以前用于对运行中的定时器进行计数。那时还使用数组timers[ ] ,using起过非常大的作用,它帮助我们记录timers[ ]已被使用到了哪个位置。另外,在不使用数组timers[ ]后,根据它是否变为0,我们才能决定应该怎样设定next。但是现在有了哨兵,就不会出现using为0的情况了。

所以,我们就让using光荣退休吧。再见了,using。感谢你一直帮助我们控制定时器,我们不会忘记你的……这当然是玩笑啦,笔者可没那么聪明能一直记住不再使用的变量。正所谓,“一切都是过眼云烟,什么都是浮云!”。ByeBye,using !(笑)。

这样一来,inthandler20就变得更清爽整洁了。

本次的timer.c节选

  1. void inthandler20(int *esp)
  2. {
  3. struct TIMER *timer;
  4. io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
  5. timerctl.count++;
  6. if (timerctl.next > timerctl.count) {
  7. return;
  8. }
  9. timer = timerctl.t0; /* 首先把最前面的地址赋给timer*/
  10. for (;;) {
  11. /* 因为timers的定时器都处于运行状态,所以不确认flags */
  12. if (timer->timeout > timerctl.count) {
  13. break;
  14. }
  15. /* 超时 */
  16. timer->flags = TIMER_FLAGS_ALLOC;
  17. fifo32_put(timer->fifo, timer->data);
  18. timer = timer->next; /* 将下一个定时器的地址赋给timer*/
  19. }
  20. timerctl.t0 = timer;
  21. timerctl.next = timer->timeout;
  22. return;
  23. }

我们进一步从settime中删除“timerctl.using++;”,从init_ pit中删除“timerctl.using=1;”。嗯,很清爽呀。

■■■■■

那么今天的任务到此就结束了。为了确认结果,我们当然要运行“make run”了。运行结果很正常。嗯……但是与harib10g比较起来,结果值还是不太理想。怎么回事呢。如果在真机上运行,是不是效果一样呢?

6 使用“哨兵”简化程序(harib10i) - 图1

稍稍变慢了?

虽然很想搞清楚原因,不过都已经这个时间了,所以今天就先到这里吧。我们明天再仔细思考。