5 加快中断处理(4)(harib10h)
我们赶紧继续做昨天没有做完的定时器改良工作吧。现在最让我们费心的是inthandler20中最后的移位处理和timer_settime中的移位处理。timer_settime虽然不是中断处理程序,但毕竟是在中断禁止期间进行的,所以必须要迅速完成。如果像现在这样,使用的定时器只有3个,用目前的处理方式还没什么问题。可当我们面对多任务时,很多应用程序同时运行,每个应用程序都使用定时器,此时如果还是使用移位处理的话,就有点浪费时间了。
在FIFO里有一个取代移位处理的方法:读取一个数据以后不是让后面的数据向前靠齐,而是改变下一次的数据读取地址。这是一个很巧妙的方法,但不适用于定时器。因为从timers[ ]中去除超时的中断时,这个方法虽然不错,但问题在于,用timer_settime登录中断时,后面的中断必须后移,在这一点上,以上方法不太好。
因此笔者再介绍一个新方法。
■■■■■
下面,我们在结构体struct TIMER中加入next变量。这是个地址变量,用来存放下一个即将超时的定时器的地址。
本次的bootpack.h节选
struct TIMER {
struct TIMER *next;
unsigned int timeout, flags;
struct FIFO32 *fifo;
int data;
};
我们还是用下面的示意图来说明结构体struct TIMER。
next表示下一个即将超时的定时器地址,图示如下。
大家也许会想,这样的图有什么用呢?图示的方法有助于理解,所以请大家坚持看下去。
■■■■■
那么,试着按照这段内容,修改定时器的中断处理程序吧。
利用next的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.timers[0]; /* 首先把最前面的地址赋给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.timers[0] = timer;
/* timerctl.next的设定 */
if (timerctl.using > 0) {
timerctl.next = timerctl.timers[0]->timeout;
} else {
timerctl.next = 0xffffffff;
}
return;
}
移位部分暂时放一放,大家先看一下超时的处理。和以前不同,对于timers[ ]数组而言,除了timers[0]以外其他完全是没有必要的。这一点是重中之重。
如果不需要超时处理,还认真地去做移位处理就完全没有道理了。所以在移位处理中只改写timers[0]。
■■■■■
下面修改timer_settime函数了。
利用next之后的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++;
if (timerctl.using == 1) {
/* 处于运行状态的定时器只有这一个时 */
timerctl.timers[0] = timer;
timer->next = 0; /* 没有下一个 */
timerctl.next = timer->timeout;
io_store_eflags(e);
return;
}
t = timerctl.timers[0];
if (timer->timeout <= t->timeout) {
/* 插入最前面的情况下 */
timerctl.timers[0] = timer;
timer->next = t; /* 下面是t */
timerctl.next = timer->timeout;
io_store_eflags(e);
return;
}
/* 搜寻插入位置 */
for (;;) {
s = t;
t = t->next;
if (t == 0) {
break; /* 最后面*/
}
if (timer->timeout <= t->timeout) {
/* 插入到s和t之间时 */
s->next = timer; /* s的下一个是timer */
timer->next = t; /* timer的下一个是t */
io_store_eflags(e);
return;
}
}
/* 插入最后面的情况下 */
s->next = timer;
timer->next = 0;
io_store_eflags(e);
return;
}
程序还是变长了。 变量t是从头开始对timers[ ]进行遍历用的,而s用来保存前一个t值。t是timer的省略(timer这个变量名已经被使用了),而s是英文字母表中t的前一个字母。
判断一下顺序,如果我们知道了插入的位置(即知道了在s和t中间插入的话),就可以像下图那样把数据重新连接起来。也就是仅仅改变s —>next和timer —>next的值就可以了。看看下图可能会更容易理解。
这样就可以不进行移位了。我们成功了!现在就算使用很多定时器,速度也不会变慢了。
这里再说几句题外话。笔者写了后面很多章之后再回过头来读这段程序,才觉得timer—>next和timerctl.next很容易混淆。timer—>next是指下一个定时器的地址,而timerctl.next是指下一个超时的时刻。所以有点后悔,如果当初将它们分别命名为next_timer和next_time就好理解多了。
笔者没有修改(主要是受到出版时间的限制),大家如果能代笔者修改一下就最好了。或者大家读这段程序时,先在脑海里自己修改一下,然后再读,也许更有助于理解。
■■■■■
话说到这里,timers[ ]已经不需要了。不过不是全都不要,最前面的timers[0]还是要保留的,它后面的部分都没有用了。因此我们来删掉这些数据。只有1个timers[0]的话,就没必要加[ ]了,所以我们把名字定义为t0好了。
本次的bootpack.h节选
struct TIMERCTL {
unsigned int count, next, using;
struct TIMER *t0;
struct TIMER timers0[MAX_TIMER];
};
相应地,也要把刚才写的inthandler20和timer_settime中的timers[0]也改写为t0。改写后的程序,没有写在这里,如果想看的话,可以浏览附带光盘中的文档。
好了,这样应该可以顺利运行了吧。应该没问题的,否则就麻烦了。真紧张呀。我们运行“make run”看看。哦,运行成功!
虽然我们还是不知道速度是否变快了,还是很满足(又来了)。但是总感觉数值变差了,难道是错觉?