6 使用“哨兵”简化程序(harib10i)
标题中略显突兀的“哨兵”一词,其实是程序技巧中的专门用语。一般来讲,说到“哨兵”,大家会想到巡逻的士兵。可一旦大家知道了这项技术的内容,肯定会认为这个名字取得太好了。总之笔者认为这是一个很棒的名称。
顺便说一下,上一节的技术称为“线性表”(linear list)。可笔者觉得这个名字不太容易理解。其实它就像挖红薯一样,我们拔第一根红薯蔓,就能挖出第一个红薯,再拔这个红薯的蔓,就能挖出下一个红薯来,所以,如果是笔者的话,就会把这种技术命名为“拔红薯式”。
■■■■■
harib10h的timer.c程序在去除移位处理后,其中的timer_settime函数还是有些冗长。浓缩的才是精华,我们来想办法简化这个程序。
我们看看程序就会发现,其中有4种可能:
运行中的定时器只有一个的情况
插入到最前面的情况
插入到s和t之间的情况
插入到最后面的情况
“既然有这么多种情况,当然要加各种条件,程序长了点也没办法”,大家可不能这样轻易放弃。如果是因为有4种可能才使程序变复杂了的话,那我们想办法消除这4种可能性不就行了吗?
我们来看看具体的做法。在进行初始化的时候,将时刻0xffffffff的定时器连到最后一个定时器上。虽然我们偷了点懒没有设定fifo等,但不必担心。反正无论如何都不可能到达这个时刻(在到达之前会修改时刻),所以不可能发生超时问题。它一直处于后面,只是个附带物,是个留下来看家的留守者。这个留守者正是“哨兵“。
加入了哨兵
void init_pit(void)
{
int i;
struct TIMER *t;
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, 0x9c);
io_out8(PIT_CNT0, 0x2e);
timerctl.count = 0;
for (i = 0; i < MAX_TIMER; i++) {
timerctl.timers0[i].flags = 0; /* 没有使用 */
}
t = timer_alloc(); /* 取得一个 */
t->timeout = 0xffffffff;
t->flags = TIMER_FLAGS_USING;
t->next = 0; /* 末尾 */
timerctl.t0 = t; /* 因为现在只有哨兵,所以他就在最前面*/
timerctl.next = 0xffffffff; /* 因为只有哨兵,所以下一个超时时刻就是哨兵的时刻 */
timerctl.using = 1;
return;
}
如果使用在12.5节中介绍的一年调整一次时刻的程序,就必须修改程序来保证不改变哨兵的时刻。比如改写成下面这样:
哨兵的时刻调整程序
int t0 = timerctl.count; /* 所有的时刻都要减去这个值 */
io_cli(); /* 在时刻调整时禁止中断 */
timerctl.count -= t0;
for (i = 0; i < MAX_TIMER; i++) {
if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
if (timerctl.timer[i].timeout != 0xffffffff) {
timerctl.timer[i].timeout -= t0;
}
}
}
io_sti();
■■■■■
由于加入了哨兵,settime的状况就变了。4种情况中有2种情况是绝对不会发生的。(下面的第1种和第4种)
处于运行中的定时器只有这1个的情况(因为有哨兵,所以最少应该有2个)
插入最前面的情况
插入s和t之间的情况
插入最后的情况(哨兵总是在最后)
所以程序能简化不少,缩短了16行。
精简后的timer_settime
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
int e;
struct TIMER *t, *s;
timer->timeout = timeout + timerctl.count;
timer->flags = TIMER_FLAGS_USING;
e = io_load_eflags();
io_cli();
timerctl.using++;
t = timerctl.t0;
if (timer->timeout <= t->timeout) {
/* 插入最前面的情况 */
timerctl.t0 = timer;
timer->next = t; /* 下面是设定t */
timerctl.next = timer->timeout;
io_store_eflags(e);
return;
}
/* 搜寻插入位置 */
for (;;) {
s = t;
t = t->next;
if (timer->timeout <= t->timeout) {
/* 插入s和t之间的情况 */
s->next = timer; /* s下一个是timer */
timer->next = t; /* timer的下一个是t */
io_store_eflags(e);
return;
}
}
}
■■■■■
现在我们终于能简化inthandler20函数了。程序最后的部分。
稍稍精简了的inthandler20
void inthandler20(int *esp)
{
int i;
struct TIMER *timer;
io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
timerctl.count++;
if (timerctl.next > timerctl.count) {
return;
}
timer = timerctl.t0; /* 首先把最前面的地址赋给timer */
for (i = 0; i < timerctl.using; i++) {
/* 因为timers的定时器都处于运行状态,所以不确认flags */
if (timer->timeout > timerctl.count) {
break;
}
/* 超时 */
timer->flags = TIMER_FLAGS_ALLOC;
fifo32_put(timer->fifo, timer->data);
timer = timer->next; /* 将下一定时器的地址代入timer*/
}
timerctl.using -= i;
/* 新移位 */
timerctl.t0 = timer;
/* timerctl.next的设定 */ /* 这里! */
timerctl.next = timerctl.t0->timeout;
return;
}
■■■■■
修改到这里以后,using就没什么用处了。……using以前用于对运行中的定时器进行计数。那时还使用数组timers[ ] ,using起过非常大的作用,它帮助我们记录timers[ ]已被使用到了哪个位置。另外,在不使用数组timers[ ]后,根据它是否变为0,我们才能决定应该怎样设定next。但是现在有了哨兵,就不会出现using为0的情况了。
所以,我们就让using光荣退休吧。再见了,using。感谢你一直帮助我们控制定时器,我们不会忘记你的……这当然是玩笑啦,笔者可没那么聪明能一直记住不再使用的变量。正所谓,“一切都是过眼云烟,什么都是浮云!”。ByeBye,using !(笑)。
这样一来,inthandler20就变得更清爽整洁了。
本次的timer.c节选
void inthandler20(int *esp)
{
struct TIMER *timer;
io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
timerctl.count++;
if (timerctl.next > timerctl.count) {
return;
}
timer = timerctl.t0; /* 首先把最前面的地址赋给timer*/
for (;;) {
/* 因为timers的定时器都处于运行状态,所以不确认flags */
if (timer->timeout > timerctl.count) {
break;
}
/* 超时 */
timer->flags = TIMER_FLAGS_ALLOC;
fifo32_put(timer->fifo, timer->data);
timer = timer->next; /* 将下一个定时器的地址赋给timer*/
}
timerctl.t0 = timer;
timerctl.next = timer->timeout;
return;
}
我们进一步从settime中删除“timerctl.using++;”,从init_ pit中删除“timerctl.using=1;”。嗯,很清爽呀。
■■■■■
那么今天的任务到此就结束了。为了确认结果,我们当然要运行“make run”了。运行结果很正常。嗯……但是与harib10g比较起来,结果值还是不太理想。怎么回事呢。如果在真机上运行,是不是效果一样呢?
稍稍变慢了?
虽然很想搞清楚原因,不过都已经这个时间了,所以今天就先到这里吧。我们明天再仔细思考。