30天自制操作系统之第12天 定时器(1)

定时器的中断处理程序要保证高效率,需要进行一些优化,这里介绍优化的方法。对于一个操作系统来说,会有多个定时器,假设该操作系统维护了500个定时器,当每一次定时中断发生时(这里我们设定1秒发生100次中断),调用中断处理程序,中断处理程序会对这500个定时器进行if判断,看哪些正在被使用,这样1秒内,就会有500X100=10000次if判断,而中断处理程序最讲究节省时间。实际上,我们不必每发生一次定时中断就去对这500个定时器进行判断。因为假设我们使用了500个定时器中的10个,而10个定时器中最小的超时时间是10s,即10s后才触发第一个定时器,而在这10s内,会进行10000X10=10万次无用的if判断,如果能省去这10万次if判断会是对性能的一次了不起的提升。这里采取的优化策略是:我们维护一个next链,即10个正在被使用的定时器按照超时时间由小到大形成next链,一开始next表示最小的超时时间(本例中为10s),这样每一次调用中断处理程序,先将当前时间count和next进行比较,如果count < next, 则说明此时没有定时间超时,就不必再对500个定时器进行判断,如果count == next,说明当前正在被使用而且都没有超时的定时器中超时时间最小的定时器到了超时时间,接下来就让这个定时器超时,并选出下一个next,即剩下的正在被使用而且没有超时的定时器中超时时间最小的定时器的超时时间。这样,随着时间的推移,每一次中断,count便加1, count会遇到next链中的各个超时时间,一旦遇到就意味着有定时器要超时了,没有遇到就说明此时没有定时器会超时,就省去了大量的无用的if判断。下面是经过优化的定时中断处理程序:

// when IRQ0(timer interrupt) happens, invoke the interrupt handler: inthandler20
void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60); // Inform PIC the information that IRQ0-00 has been received.
	timerctl.count++; // each second it adds 100
	if (timerctl.next > timerctl.count) {
		return; // the next time has not arrived, so finish
	}
	timerctl.next = 0xffffffff;
	int i;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			if (timerctl.timer[i].timeout <= timerctl.count) {
				// timeout 
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			} else {
				// not timeout
				if (timerctl.next > timerctl.timer[i].timeout) {
					timerctl.next = timerctl.timer[i].timeout; // elect the next time of timeout
				}
			}
		}
	}
	return;
}

其实我们的优化工作并没有结束,在count到达next时刻和没到next时刻的定时中断,它们的处理时间差别很大(没到next,只执行一个判断就return,而到达next,要对500个定时器进行判断)。这样的程序结构不好。因为平常运行一直都很快的程序,会偶尔由于中断处理拖得太长,而搞得像是主程序要停了似的,更确切一点,这样有时会让人觉得“不知为什么,鼠标偶尔会反应迟钝,很卡”,因为此时处理器正忙于定时中断处理程序对500个定时器的if判断。因此我们要让到达next时刻的定时器中断的处理时间再缩短一些。我们再维护一个数组timers,只存储当前正在使用而且未超时的定时器的地址,当到达next时刻,不用再对500个定时器进行判断,只需对timers数组中的定时器进行判断,看究竟是哪个定时器超时了,然后重新对timers数组进行移位,去除刚才超时了的定时器。下面是再次优化后的定时中断处理程序:

// when IRQ0(timer interrupt) happens, invoke the interrupt handler: inthandler20
void inthandler20(int *esp)
{
	int i, j;
	io_out8(PIC0_OCW2, 0x60); // Inform PIC the information that IRQ0-00 has been received.
	timerctl.count++; // each second it adds 100
	if (timerctl.next > timerctl.count) {
		return; // the next time has not arrived, so finish
	}
	// checking in all of the timers which are being used, which timer time out.
	// now we do not have to do MAX_TIMER times of 'if'
	for (i = 0; i < timerctl.using; i++) {
		if (timerctl.timers[i]->timeout > timerctl.count) {
			break;
		}
		// timeout
		timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
		fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
	}
	// a timer has timed out, we need shift the rest using timer
	timerctl.using -= i;
	for (j = 0; j < timerctl.using; j++) {
		timerctl.timers[j] = timerctl.timers[i + j];
	}
	if (timerctl.using > 0) {
		timerctl.next = timerctl.timers[0]->timeout;
	} else {
		timerctl.next = 0xffffffff;
	}
	return;
}


上面提到移位,对于定时器少的情况,移位对性能的影响不大, 但对于多任务,很多应用程序同时运行,每个程序都使用定时器,如果还使用移位的话,就有点浪费时间了。尤其在中断处理程序中进行大量的移位,更是不优雅。

我们用struct TIMER表示一个定时器

struct TIMER {
	struct TIMER *next; // next points to the address of next timer which is going to timeout
	unsigned int timeout, flags; // flags is used to record the status of the timer
	struct FIFO32 *fifo;
	int data;
};

我们的具体做法是,当一个定时器超时时,通过next将下一个要超时的定时器的地址赋给存放所有定时器地址的数组的第一位,那么下次超时出现时,数组的第一位存的就是当前发生超时的定时器的地址。也就是我们不必再维护一个存有正在使用定时器地址的数组了,只需设一个变量t0用来存储下一个要超时的定时器的地址,所有这些简化都要归功于一开始创建各个定时器时,根据超时时间,用各自的属性next,以链表的方式从早到晚链接起来,设想一下,第一个(也就是最早的)定时器超时了,将它的next赋给t0,那么t0存的就是下一个超时的定时器的地址,依次进行下去。这样就简化了不少,而且逻辑清晰。



最后再介绍一个程序技巧——”哨兵“。经过上面的优化后,为了产生上面的线性表,每当一个定时器被定义出来时,都要找准自己的位置,插入到线性表中,这时会有四种情况:

  1. 插入的定时器是第一个定时器
  2. 插入位置在线性表的最前面
  3. 插入位置在中间
  4. 插入位置在线性表最后面
我们每定义一个定时器,都要进行四种情况的判断,有点不优雅。如果使用了哨兵,会去掉情况1和4。先来看一下什么是”哨兵“——
在初始化时,将时刻0xffffffff(这个时刻是最大的超时时间)的定时器连到最后一个定时器上,它一直处于后面,只是个附带物,是个留下来看家的留守者,这个留守者正是”哨兵“。
由于有超时时间为0xffffffff的定时器——哨兵存在,所以情况1不会存在,同样,要插入的定时器也不会插在最后,哨兵已经上岗了。这样四种情况的判断缩减到了两种,有利于提升性能。



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值