欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > 【读书笔记-《30天自制操作系统》-11】Day12

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

2024/10/25 9:32:26 来源:https://blog.csdn.net/Ocean1994/article/details/141718020  浏览:    关键词:【读书笔记-《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	0x0040void 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	ESPUSH	DSPUSHADMOV		EAX,ESPPUSH	EAXMOV		AX,SSMOV		DS,AXMOV		ES,AXCALL	_inthandler20POP		EAXPOPADPOP		DSPOP		ESIRETD
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;
}

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com