【读书笔记-《30天自制操作系统》-11】Day12

从本篇内容开始讲解定时器。本篇内容比较简单,首先介绍定时器的概念与设置方法,然后介绍超时的中断处理,并对中断处理函数进行了优化。
在这里插入图片描述

1. 定时器

定时器是操作系统中十分重要的功能。它的原理很简单,只是每隔一段时间发送一个中断给CPU。如果想要知道过了多长时间,只需要在中断处理程序中记录中断的次数就可以计算得到。

管理定时器也很简单,只需要对PIT(Programmable Interval Timer的缩写)进行设置即可。通过对PIT的设置,可以设置定时器每隔多长时间发送一次中断。PIT连接着IRQ的0号中断。设置PIT需要的命令如下:

  • AL = 0x34;OUT(0x43, AL);
  • AL = 中断周期的低8位;OUT(0x40, AL)
  • AL = 中断周期的高8位;OUT(0x40, AL)

实际设置的中断频率 = 单位时间时钟周期数(主频)/设定的数值
根据当前的主频,如果设定11932,则中断频率位100Hz,即10ms发生一次中断。把11932换算为16进制,则是0x2e9c。

设置PIT相关程序如下:

#define PIT_CTRL	0x0043
#define PIT_CNT0	0x0040

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

在主程序中调用init_pit函数:

struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], keybuf[32], mousebuf[128];
	int mx, my, i;
	unsigned int memtotal, count = 0;
	struct MOUSE_DEC mdec;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct SHTCTL *shtctl;
	struct SHEET *sht_back, *sht_mouse, *sht_win;
	unsigned char *buf_back, buf_mouse[256], *buf_win;

	init_gdtidt();
	init_pic();
	……

涉及到了中断,则需要将中断处理函数注册到IDT中,这些也是前面讲过的内容了。

	/* 注册IDT*/
	set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
_asm_inthandler20:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler20
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD
void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	/* 通知PIC, IRQ-00中断受理完毕 */
	/* 暂时无内容 */
	return;
}

这样定时器的中断处理就完成了。接下来就在中断处理中进行计时:

struct TIMERCTL {
	unsigned int count;
};

struct TIMERCTL timerctl;

void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	return;
}

新建了timerctl结构体,每次进入中断处理函数时就对其中的count变量增加1。在主程序中将count实时显示出来,根据之前的设置,每秒钟显示的数值会增加100。

	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);
		……

这样就可以测量一段确定的时间间隔了。
在这里插入图片描述
2. 超时与中断处理

2.1 超时处理

可以通过定时器记录一段时间间隔,那么操作系统就可以实现这样一种功能:
经过一段时间间隔后,进行某种特定的操作。这样的功能就被称为超时(timeout)。

为了实现这一功能,首先完善结构体TIMERCTL:

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

其中timeout用来记录距离超时还有多长时间,这个时间达到0,则程序向缓冲区fifo中发送数据,这样来通知操作系统。
相关的函数修改如下:

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);	
	timerctl.count++;
	if (timerctl.timeout > 0) { /* 设置好了超时时间 */
		timerctl.timeout--;
		if (timerctl.timeout == 0) {
			fifo8_put(timerctl.fifo, timerctl.data);
		}
	}
	return;
}

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
	int eflags;
	eflags = io_load_eflags();
	io_cli();
	timerctl.timeout = timeout;
	timerctl.fifo = fifo;
	timerctl.data = data;
	io_store_eflags(eflags);
	return;
}

在init_pit函数中首先将count和timeout变量初始化为0,而settimer函数用于给结构体其他成员赋值,尤其是设置好timeout。超时后的实际操作放在了inthandler20函数中实现。为了防止中断混乱,在settimer函数中还是先禁用了中断。
结合以上函数,在主程序中进行如下的实现:

void HariMain(void)
{
	……
	struct FIFO8 timerfifo;
	char s[40], keybuf[32], mousebuf[128], timerbuf[8];
	……
	fifo8_init(&keyfifo, 32, keybuf);
	settimer(1000, &timerfifo, 1);
	……
	for (;;) {
		……
		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 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);
			}
		}
	}
}

可以看出在10s后向timerfifo中写入了数据1,在主程序中,接收到数据时就在屏幕上显示10[sec]。

能够实现过10s显示一次,自然也可以调整时间的长度。并且操作系统中可能用到多个定时器,这样就可以将程序进行扩展:

#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];
};

扩充了定时器的数量,其他程序也需要进行相应的修改:

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

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; /* ���g�p */
	}
	return;
}

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);	
	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;
}

类似的程序在内存分配与图层管理中都能看到,关于初始化、分配、释放等函数就不必细说了。在中断处理函数inthandler20中,每次进入中断函数,都会把所有运行中的定时器检查一遍,看是否超时。如果超时,则向fifo中写入数据。这样就可以设置多个定时器,设置不同的超时时间了。

2.2 加快中断处理

但这里有个问题。前面中断的内容中已经讲过,中断处理函数必须在短时间内完成,否则会耽误CPU的正常工作。而我们为了实现超时,每次进入inthandler20都要对所有活动中的定时器执行timeout–操作,即CPU要先读出变量的值,执行减法运算,再写回内存中,这样浪费了很多时间。是否可用对这种实现方式进行优化呢?

我们不用timeout来表示剩余多少时间超时,而是用来表示超时的时刻。因为当前的时刻可用通过count进行计数,每次中断时只需要比较count的值是否达到timeout,就可以判断是否超时了。这样就减少了每次中断CPU所需要做的减法运算,加快了中断的处理。

void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	
	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;
}

相应地也需要修改timer_settime函数,将超时时刻设置为当前时刻加上超时时间:

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	return;
}

这样依赖程序确实得到优化了。但还没完,用来计时的变量count总有溢出的时候,0xffffffff表示的时间转换成天大约是497天。为了保证超时功能的正常,没过大约一年的时间需要把count重新调整归零:

int t0 = timerctl.count;/* 所有定时器的时刻都要减去这个值*/
io_cli();/* 调整时刻时需要禁止中断 */
timerctl.count -= t0;
for(i = 0;i < MAX_TIMERS; i++)
{
	if(timerctl.timer[i].flags == TIMER_FLAGS_USING)
	{
		timerctl.timer[i].timeout -= t0;
	}
}
io_sti();

已经做了以上的改进和优化,但其实中断处理程序还有改进的空间。
inthandler20其实是定时器的中断,每隔10ms就会产生一次中断并进入中断处理函数,这样每秒钟要进入100次中断函数。而每次中断函数都要执行500次的if判断检查运行中的定时器,一秒钟就要运行50000次。但真正产生超时,更改flags的值,执行fifo8_put函数,一秒钟最多也就2次,其他的大量if判断其实都是在做无用功。

为了避免这么多次无用且浪费时间的if判断,采用这种思路。超时时间有长短的不同,每次进入中断的时候,我们只需要关注当前最近的一个即将超时的定时器就可以了。我们把当前最近的一个超时时刻记录下来,每次进入中断的时候先与当前的时刻进行比较。如果最近的超时时刻没有达到,说明没有超时,这样就直接返回;如果发现达到了最近的超时时刻,再检查一遍定时器,找出这个超时的定时器,并且更新下一个最近的超时时刻。
中断处理的过程:

struct TIMERCTL {
	unsigned int count, next;
	struct TIMER timers0[MAX_TIMER];
};

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) {
				/* 找到了超时的定时器,设置flags */
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			} else {
				/* 其他未超时的定时器中更新next */
				if (timerctl.next > timerctl.timer[i].timeout) {
					timerctl.next = timerctl.timer[i].timeout;
				}
			}
		}
	}
	return;
}

初始化与设置超时时间:

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) {
		/* 更新下一次的超时时刻 */
		timerctl.next = timer->timeout;
	}
	return;
}

其实到这里已经优化的不错了。但是吹毛求疵一点,未达到超时时刻,处理的速度很快;达到超时时刻了,处理的时间就明显增加,作者认为这样可能会导致偶尔很卡的问题。(这一点说实话自己完全想不到)

为了减少达到超时时刻后进行处理所用的时间,采用的措施是把定时器按照超时时间先后排好顺序,再记录下运行中的定时器个数,这样在查找的时候就不需要把所有的定时器都检查一遍了。

struct TIMERCTL {
	unsigned int count, next, using;
	struct TIMER *timers[MAX_TIMER];
	struct TIMER timers0[MAX_TIMER];
};

结构体中timers用于存储当前运行中的定时器,并且按照时间顺序排列好,using变量则用来存放当前运行中的定时器数量。

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);
	}
	/* 有i个定时器超时,则timers中存放的其余定时器进行移位*/
	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;
}

另外还需要修改的是timer_settime函数,在设置的时候需要将timer注册到timers数组中的正确位置。同样在注册的时候也要先禁用中断。

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号之后全部后移一位,腾出一个空位 */
	for (j = timerctl.using; j > i; j--) {
		timerctl.timers[j] = timerctl.timers[j - 1];
	}
	timerctl.using++;
	/* 插入到空位上 */
	timerctl.timers[i] = timer;
	timerctl.next = timerctl.timers[0]->timeout;
	io_store_eflags(e);
	return;
}

到这里本篇的内容就完成了。本篇的内容还是比较简单的,也可以看出作者对与程序的运行有种执念,不只是要程序能够正常运行,还要进行种种优化。下一篇的内容仍然是定时器,可以看出作者对定时器内容的重视。敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值