第12天:定时器(1)

12.1、设定定时器

定时器每个一定时间就会往CPU发送一个时间中断,就是IRQ0,0x20号中断,这样CPU就不会自己去计算时间了,电脑中负责计时的是PIT(programmable interval timer),就是”可编程的间隔型定时器“,PIT连接着IRQ0,每当发生中断,PIT会通过PIC的IRQ0发给CPU中断消息,CPU就会处理时间中断信息。

12.1.1、设置IRQ0中断频率

中断频率
向0x43端口写0x34:要设定时钟频率
向0x40端口写两个八位数据即可设定中断频率。
时间中断频率 = 芯片频率 / 写入的两个八位数据

如果PIT还是8254,写入11932就可以设定每0.01秒产生一次时间中断。

12.2、设计时钟中断函数

1、IRQ0中断号为0x20,在IDT里注册0x20号中断。

2、设计中断相应函数

struct TIMERCTL {
	unsigned int count;
};
struct TIMERCTL timerctl;

void init_pit(void)
{
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	return;
}

void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	/* IRQ0 已受理 */
	timerctl.count++; // 每中断一次,count加一
	return;
}

中断信息显示在窗口上

for (;;) {
		sprintf(s, "%010d", timerctl.count);
		boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
		putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
		sheet_refresh(sht_win, 40, 28, 120, 44);

12.3、超时

现在已经有了时间中断,但是需要统计过了多少时间,多少时间之后需要做什么事情。

struct TIMERCTL {
	unsigned int count;
	unsigned int timeout;
	struct FIFO8 *fifo;
	unsigned char data;
};

timeout:多少时间后超时
fifo:超时后往缓冲区写入数据
data:设置向缓冲区写入的数据

// timer.c
#define PIT_CTRL	0x0043
#define PIT_CNT0	0x0040

struct TIMERCTL timerctl;

void init_pit(void)
{
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	timerctl.timeout = 0;
	return;
}

void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	/* IRQ0受理 */
	timerctl.count++;
	if (timerctl.timeout > 0) { /* 如果设定了超时时间 */
		timerctl.timeout--;
		if (timerctl.timeout == 0) {
			fifo8_put(timerctl.fifo, timerctl.data); // 向缓冲区写入数据data
		}
	}
	return;
}

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
	int eflags;
	eflags = io_load_eflags(); // 如果还没设置超时时间,就响应IRQ0的中断的话,会造成错误,所以需要在设置超时时间时关闭中断,等设置完成后恢复寄存器值
	io_cli();
	timerctl.timeout = timeout; // 设置超时时间
	timerctl.fifo = fifo; // 设置fifo的地址,该结构体可以找到缓冲区地址
	timerctl.data = data; // 往缓冲区写入的数据
	io_store_eflags(eflags);
	return;
}

HariMain函数中

else if (fifo8_status(&timerfifo) != 0) {
	i = fifo8_get(&timerfifo); // 首先读入(为了设定起始点)
	io_sti();
	putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
	sheet_refresh(sht_back, 0, 64, 56, 80);
}

12.4、设定多个定时器

结构体

// 最大可定义500个定时器
#define MAX_TIMER		500
struct TIMER {
	unsigned int timeout, flags;
	struct FIFO8 *fifo;
	unsigned char data;
};
struct TIMERCTL {
	unsigned int count;
	struct TIMER timer[MAX_TIMER];
};

定时器配置

// timer.c

#define PIT_CTRL	0x0043
#define PIT_CNT0	0x0040

struct TIMERCTL timerctl;

#define TIMER_FLAGS_ALLOC		1	/* 已配置状态 */
#define TIMER_FLAGS_USING		2	/* 定时器运行中 */

// flags设置为0,标记定时器未分配
void init_pit(void)
{
	int i;
	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.timer[i].flags = 0; /* 未使用 */
	}
	return;
}

// 找到flags为0的定时器,分配出去,标记为配置状态
struct TIMER *timer_alloc(void)
{
	int i;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == 0) {
			timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
			return &timerctl.timer[i];
		}
	}
	return 0;
}

// 定时器不在使用,释放
void timer_free(struct TIMER *timer)
{
	timer->flags = 0; /* 未使用 */
	return;
}

// 定时器中断写入缓存的地址和数据
void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
{
	timer->fifo = fifo;
	timer->data = data;
	return;
}

// 设定定时器超时时间,并设置为定时器运行中
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout;
	timer->flags = TIMER_FLAGS_USING;
	return;
}

// 定时器中断处理函数
void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	/* IRQ0受理 */
	timerctl.count++;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			timerctl.timer[i].timeout--;
			if (timerctl.timer[i].timeout == 0) {
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			}
		}
	}
	return;
}

HariMain函数

void HariMain(void)
{
(略)
	struct FIFO8 timerfifo, timerfifo2, timerfifo3;
	char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8];
	struct TIMER *timer, *timer2, *timer3;

(略)

	fifo8_init(&timerfifo, 8, timerbuf);
	timer = timer_alloc();
	timer_init(timer, &timerfifo, 1);
	timer_settime(timer, 1000);
	fifo8_init(&timerfifo2, 8, timerbuf2);
	timer2 = timer_alloc();
	timer_init(timer2, &timerfifo2, 1);
	timer_settime(timer2, 300);
	fifo8_init(&timerfifo3, 8, timerbuf3);
	timer3 = timer_alloc();
	timer_init(timer3, &timerfifo3, 1);
	timer_settime(timer3, 50);

(略)

	for (;;) {

(略)

		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo)
				+ fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) {
			io_sti();
		} else {
			if (fifo8_status(&keyfifo) != 0) {
				(略)
			} else if (fifo8_status(&mousefifo) != 0) {
				(略)
			} else if (fifo8_status(&timerfifo) != 0) {
				i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */
				io_sti();
				putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
				sheet_refresh(sht_back, 0, 64, 56, 80);
			} else if (fifo8_status(&timerfifo2) != 0) {
				i = fifo8_get(&timerfifo2); /* 首先读入(为了设定起始点) */
				io_sti();
				putfonts8_asc(buf_back, binfo->scrnx, 0, 80, COL8_FFFFFF, "3[sec]");
				sheet_refresh(sht_back, 0, 80, 48, 96);
			} else if (fifo8_status(&timerfifo3) != 0) { // 模拟光标闪烁
				i = fifo8_get(&timerfifo3);
				io_sti();
				if (i != 0) {
					timer_init(timer3, &timerfifo3, 0); /* 设置为0 */
					boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
				} else {
					timer_init(timer3, &timerfifo3, 1); /* 设置为1 */
					boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
				}
				timer_settime(timer3, 50);
				sheet_refresh(sht_back, 8, 96, 16, 112);
			}
		}
	}
}

12.5、加快中断处理(1)

目前为止,超时的做法都是,给每个定时器设定一个时间,每次中断把所有定时器都检查一遍,并且把每个定时器的timeout减一,CPU一直要计算,这样效率必然不高。

改进:
时间是一维,超时响应必然是按顺序的。
比如:现在是两点,设置两个定时器,分别是三点和四点响应,那么就设置定时器为三点四点,指针走到三点四点就会去响应。不用设置超时时间分别为一个小时和两个小时,然后一直去检查并且倒计时。

现在这个count就是那个指针,它每次中断都会加一,证明时间过了0.01秒。每次循环就去问一下,到三点了吗?到四点了吗?到了就响应(往缓存写入数据),没有就不响应。

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout + timerctl.count; // 设定哪一时刻超时
	timer->flags = TIMER_FLAGS_USING;
	return;
}

void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	/* IRQ0受理 */
	timerctl.count++;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			if (timerctl.timer[i].timeout <= timerctl.count) {
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			}
		}
	}
	return;
}

但这也有个明显缺陷,就是count是四字节,如果溢出了就不行了,因为现在是0.01秒中断一次,count加一,所以简单计算一下,count在497天后耗尽。

12.6、加快中断处理(2)

从计算改善到逻辑判断,虽然加快了,但是每次还要检查所有时钟,有些三点的还没响应,还要检查四点了,完全没有必要。 所以据此再做改进:

struct TIMER {
	unsigned int timeout, flags;
	struct FIFO8 *fifo;
	unsigned char data;
};
struct TIMERCTL {
	unsigned int count, next; // 增加了next变量,用来指定下一个要响应的定时器
	struct TIMER timer[MAX_TIMER];
};

改进后的中断响应代码:

// timer.c
void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	if (timerctl.next > timerctl.count) {
		return; /* 三点还没到,其他的定时器就不要检查了 */
	}
	timerctl.next = 0xffffffff;
	for (i = 0; i < MAX_TIMER; i++) { // 如果有超时,再遍历找到超时的那个定时器
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			if (timerctl.timer[i].timeout <= timerctl.count) {
				/* 找到超时的定时器,往缓存中写入data */
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			} else {
				/* 如果还没超时,就复制给next,最后next保存的就是下一个超时最近的定时器的超时时间 */
				if (timerctl.next > timerctl.timer[i].timeout) {
					timerctl.next = timerctl.timer[i].timeout;
				}
			}
		}
	}
	return;
}

因为next加入,需要更改以下两个函数

void init_pit(void)
{
	int i;
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	timerctl.next = 0xffffffff; /* 最初没有正在运行的定时器 */
	for (i = 0; i < MAX_TIMER; i++) {
		timerctl.timer[i].flags = 0; /* 未使用 */
	}
	return;
}

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	if (timerctl.next > timer->timeout) {
		/* 设置next为最近的超时定时器的超时时间 */
		timerctl.next = timer->timeout;
	}
	return;
}

12.7、加快中断处理(3)

第二次改进后,发现当没有超时的时候,处理很快,当有超时的时候处理还是很复杂,极端情况下还是会遍历所有定时器。可能会导致鼠标有时卡顿之类的。
那,再次改进吧。

怎么办呢,第二次改进之后,即使知道有超时,还是要进行不知道多少次的循环,找到超时的定时器,解决这个痛点,可以先按照超时时间给定时器们排个序,那么每次超时的必然都会是第一个或最后一个,当然这里把最快超时的排在前面。

#define MAX_TIMER		500
struct TIMER {
	unsigned int timeout, flags;
	struct FIFO8 *fifo;
	unsigned char data;
};
struct TIMERCTL {
	unsigned int count, next, using; // 加了using变量,用来指定现在使用的有多少个定时器
	struct TIMER *timers[MAX_TIMER]; // 按照超时时间来排序定时器,跟图层排序一样
	struct TIMER timers0[MAX_TIMER];
};

中断响应函数改进:

void inthandler20(int *esp)
{
	int i, j;
	io_out8(PIC0_OCW2, 0x60);
	timerctl.count++;
	if (timerctl.next > timerctl.count) { // 最近一个都没超时,其他的不要看了
		return;
	}
	for (i = 0; i < timerctl.using; i++) { // 遍历排好序的、在使用的定时器
		if (timerctl.timers[i]->timeout > timerctl.count) {// 如果没有超时就退出循环
			break;
		}
		// 超时后,flags设置配置状态,并把数据写入缓存
		timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
		fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
	}
	timerctl.using -= i; // 删除已经超时的
	for (j = 0; j < timerctl.using; j++) { // 往前移动定时器
		timerctl.timers[j] = timerctl.timers[i + j];
	}
	if (timerctl.using > 0) { // 如果还有定时器,next设置为最近要超时的定时器
		timerctl.next = timerctl.timers[0]->timeout;
	} else { // 反之
		timerctl.next = 0xffffffff;
	}
	return;
}

设定超时的时候,给它们排好序:

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	int e, i, j;
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	e = io_load_eflags();
	io_cli(); // 设定时钟时要关闭中断
	/* 找到要设定时钟应该插入的位置 */
	for (i = 0; i < timerctl.using; i++) {
		if (timerctl.timers[i]->timeout >= timer->timeout) {
			break;
		}
	}
	/* 插入位置i,把i之后的向后移动一个 */
	for (j = timerctl.using; j > i; j--) {
		timerctl.timers[j] = timerctl.timers[j - 1];
	}
	timerctl.using++; // 定时器加一
	timerctl.timers[i] = timer; // 在第i个位置插入
	timerctl.next = timerctl.timers[0]->timeout; // next设置为最近超时的定时器
	io_store_eflags(e); // 恢复中断
	return;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值