平台
处理器:Intel Celeron® Dual-Core CPU 2.10GHz
操作系统:Windows7 专业版 x86
阅读书籍:《30天自制操作系统》—川合秀实[2015.04.12--04.14]
工具:../toolset/
1 通路(框架)
1.1 使用定时器中断的步骤(硬件接口)
要在电脑中使用定时器,需要对PIT(Programmable Interval Time,可编程的间隔型定时器)进行设定。PIT链接PIC的IRQ0,设置PIT包括设定向IRQ0发起中断的时间间隔。电脑里的定时器用一块单独的芯片(如8254)实现,要设置PIT时需要查看PIT所在芯片的手册。
设置PIT以一定中断周期向IRQ0发起中断的步骤为:
- 初始化PIT,允许PIC检测IRQ0中断
AL= 中断周期的低8位;OUT(0x40,AL);
AL= 中断周期的高8位;OUT(0x40,AL);
中断产生的频率为PIT频率(1.19318MHz)除以往端口40h中设定的值。如果指定中断周期为0,会被看作是指定了65536。
- 编写IRQ0的中断处理程序。
- 在IDT中注册IRQ0的中断处理程序。
1.2 实现定时器中断
(1) 初始化PIT
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
#define PIT_CT_H 0x2e
#define PIT_CT_L 0x9c
struct TIMERCTL {
unsigned int count;
};
struct TIMERCTL timerctl;
void init_pit(void)
{
io_out8(PIT_CTRL, 0x34);
// 10ms
io_out8(PIT_CNT0, PIT_CT_H);
io_out8(PIT_CNT0, PIT_CT_L);
timerctl.count = 0; //随便做点计数初始化类的工作
return;
}
在HariMain()(或者其它合适地方)开启检测IRQ0中断:
init_pit();
io_out8(PIC0_IMR, 0xf8);//检测PIT,键盘,IRQ2
io_out8(PIC1_IMR, 0xef);//鼠标中断检测
IRQ0在1s内就会发生100000次。
(2) 编写IRQ0的中断处理程序
在设置主PIC时,将PIC的IRQ0-7设置成了由INT20-27接收。所以,PIT中断号为20。
void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
timerctl.count++; //每隔10us增计一次数
return;
}
为了使用中断的返回IRET指令,在naskfunc.nas汇编程序中调用inthandler20():
1. GLOBAL _asm_inthandler20
2. EXTERN _inthandler20
3. ……
4. _asm_inthandler20:
5. PUSH ES
6. PUSH DS
7. PUSHAD
8. MOV EAX,ESP
9. PUSH EAX
10. MOV AX,SS
11. MOV DS,AX
12. MOV ES,AX
13. CALL _inthandler20
14. POP EAX
15. POPAD
16. POP DS
17. POP ES
18. IRETD(3) 注册IRQ0的中断处理程序
在init_gdtidt()函数中注册IRQ0中断处理函数:
set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); //PIT(定时器)
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); //IRQ12(鼠标)
(4) 结果显示
在HariMain()中显示计数结果:
extern struct TIMERCTL timerctl;
……
void HariMain(void)
{
……
for (;;) {
sprintf(str, "%010d", timerctl.count);
boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, str);
sheet_refresh(sht_win, 40, 28, 120, 44);
io_cli();
……
}
}
完成函数、全局变量声明、修改Makefile工作。打开”!cons_nt.bat”运行程序:
Figure1. 使用定时器定时产生中断(QEMU 1s增100)
2 组织(设计)
打开使用定时器中断功能步骤后,就可以对其进行许多逻辑上的设计。让定时功能变得复杂些,根据功能先设计一个数据结构,然后所有的子函数都围绕这个数据结构来编写。
2.1 管理多个定时器的结构体
#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]; //MAX_TIMER个定时器
};
2.2 初始化PIT和管理定时器结构体
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
#define PIT_CT_H 0x2e//0x52
#define PIT_CT_L 0x9c//0x08
struct TIMERCTL timerctl;
//初始化PIT和定时器结构体
void init_pit(void)
{
int i;
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, PIT_CT_L);
io_out8(PIT_CNT0, PIT_CT_H);
timerctl.count = 0;
for (i = 0; i < MAX_TIMER; i++) {
timerctl.timer[i].flags = 0; //未使用
}
return;
}
将各个定时器(结构体)标识为未使用状态。
2.3 配置初始化 释放定时器
#define TIMER_FLAGS_ALLOC 1 //已配置
/* 配置一个未使用的定时器结构体
* 返回配置结构体的首地址
*/
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; //无定时器结构体处于未使用状态
}
/* 设置定时器管理缓冲区的结构体,设置定时到达往缓冲区发送的数据
* timer为某描述定时器的结构体的首地址
* fifo为管理缓冲区的结构体
* data为定时到达时往缓冲区发送的数据
*/
void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
{
timer->fifo = fifo;
timer->data = data;
return;
}
/* 设置定时器定时时间
* timer为某描述定时器的结构体的首地址
* timeout为定时器定时时间
*/
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
timer->timeout = timeout;
timer->flags = TIMER_FLAGS_USING;
return;
}
/* 释放timer指向的定时器
*/
void timer_free(struct TIMER *timer)
{
timer->flags = 0; /* 未使用 */
return;
}
2.4 中断处理函数
void inthandler20(int *esp)
{
int i;
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
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;
}
2.5 主函数对子函数的调用
void HariMain(void)
{
……
struct FIFO8 timerfifo, timerfifo2, timerfifo3;
char str[40], 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); //定时器定时数值为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 (;;) {
……
if ( fifo8_status(&timerfifo) \
+ fifo8_status(&timerfifo2) \
+ fifo8_status(&timerfifo3) \
+ fifo8_status(&mousefifo) == 0) {
io_sti(); //---CPU无需休眠,高速刷新图层
} 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);
boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
} else {
timer_init(timer3, &timerfifo3, 1);
boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
}
timer_settime(timer3, 50);
sheet_refresh(sht_back, 8, 96, 16, 112);
}
}
}
整个程序在”!cons_nt.bat”中通过编译后,使用“makerun命令运行:
Figure2. 定时10,3s 光标闪烁程序运行结果
3 提升(优化)
列出提升程序的3各方面,主要按照“书”中的方式提升了中断程序的效率。注意程序提升的前提,如果没有必要就不要“画龙点睛”。
3.1 功能
如定时器的count变量,其计数有一个上限,当计数到上限时应做相应的处理(如归0)。
3.2 效率
考虑程序效率(空间,运行)的前提是这段程序被运行的频率较高。对于频率较高的中断来说,中断处理程序的运行时间能被减少一点是一点。在QEMU虚拟机中,定时器中断的频率为100Hz,这不算太快,还是按照“书”中那样,想办法来提升定时器中断程序的运行效率。在2中,设计了可由一个定时器硬件得到多个定时器(逻辑)的程序,其中断程序如下。
void inthandler20(int *esp)
{
int i;
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
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;
}
(1) 减少对内存多次访问的语句
“timerctl.timer[i].timeout--;”语句在for循环内,每一个正在使用的定时器都会执行它(从内存取,减1,再写入内存;如果cache能够解决这个问题,此语句也不存在对内存多次访问)。为了消除在for语句中多次防内存的语句,“书”将“timerctl.timer[i].timeout”含义变为定时的目标值:
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
timer->timeout = timeout + timerctl.count;
timer->flags = TIMER_FLAGS_USING;
return;
}
中断程序中的自减语句就可以被改为比较语句:
……
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);
}
}
}
(timerI-IIe)
(2) 消除不必要的判断语句
定时器中断程序每秒要执行100次,每执行一次定时器中断程序就会执行500次if语句。处于TIMER_FLAGS_USING状态的定时器可能原本就很少,再加上处于TIMER_FLAGS_USING状态的定时器到处于TIMER_FLAGS_ALLOC状态的转变,很多if判断语句都是在做无用功。“书”中往定时器结构体中添加了一个名为next的变量来消除不必要的if语句的执行。
struct TIMERCTL { //管理定时器的结构体
unsigned int count, next; //计数,记录“下一次”的定时值
struct TIMER timer[MAX_TIMER]; //MAX_TIMER个定时器
};
struct TIMERCTL timerctl;
//初始化PIT和定时器结构体
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;
}
/* 设置定时器定时的数值,下一时刻的数值
* timer为某描述定时器的结构体的首地址
* timeout为定时器定时目标值
*/
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
timer->timeout = timeout + timerctl.count;
timer->flags = TIMER_FLAGS_USING;
if (timerctl.next > timer->timeout) {
//由于timer_settime会被每个定时器调用,
//所以next的值为所有定时器timeout中的最小值
timerctl.next = timer->timeout;
}
return;
}
定时器结构体内的next变量用来存储所有定时器中timeout的最小值,根据next变量的含义修改定时器中断处理函数:
void inthandler20(int *esp)
{
int i;
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
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) {
timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
} else {//重新获取定时器中timeout的最小值
if (timerctl.next > timerctl.timer[i].timeout) {
timerctl.next = timerctl.timer[i].timeout;
}
}
}
}
return;
}
在定时器中断程序中如此使用next变量后,只要计数还未到定时器中的最小值,在“timerctl.count++;”语句后就会直接返回。
只当count计数到next后,if语句才被执行500次。在flag为TIMER_FLAGS_USING状态的定时器中,timeout值最小的定时器将会赋予TIMER_FLAGS_ALLOC状态并同时向缓冲区发送数据;其余定时器的timeout值将会被比较,next最后得到各定时器中最小的timeout值。(timerI-IIf)
(3) 缩短到达next时定时器中断程序的运行时间
加入next后,到达next时刻和没到next时刻的定时器中断运行的时间差别很大。这样的程序结构不好。因为平常运行一直很快的程序,会偶尔由于中断处理拖得太长,而搞得像是主程序要停了似的。(“书”中原文),“书”从管理定时器的结构体出发,修改了某些程序。
struct TIMERCTL { //管理定时器的结构体
unsigned int count, next, using; //计数,记录“下一次”的定时值 ,正在使用的定时器的个数
struct TIMER timers0[MAX_TIMER]; //MAX_TIMER个定时器
struct TIMER *timers[MAX_TIMER]; //记录MAX_TIMER的地址
};
struct TIMERCTL timerctl;
//初始化PIT和定时器结构体
void init_pit(void)
{
int i;
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, PIT_CT_L);
io_out8(PIT_CNT0, PIT_CT_H);
timerctl.count = 0;
timerctl.next = 0xffffffff; //没有运行的定时器,将其值设置为最大
timerctl.using = 0;
for (i = 0; i < MAX_TIMER; i++) {
timerctl.timers0[i].flags = 0; //未使用
}
return;
}
#define TIMER_FLAGS_ALLOC 1 //已配置
#define TIMER_FLAGS_USING 2 //正在定时
/* 配置一个未使用的定时器结构体
* 返回配置结构体的首地址
*/
struct TIMER *timer_alloc(void)
{
int i;
for (i = 0; i < MAX_TIMER; i++) {
if (timerctl.timers0[i].flags == 0) {
timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
return &timerctl.timers0[i];
}
}
return 0; //无定时器结构体处于未使用状态
}
/* 将定时器timer添加到所有定时器的正确的位置,
* 所有的定时器按照timeout的值从小到大排列,在此过程中不允许定时器中断发生
* timer为某描述定时器的结构体的首地址
* timeout为定时器定时目标值
*/
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();
//通过管理定时器的结构体找到比timer的timeout大的位置
for (i = 0; i < timerctl.using; i++) {
if (timerctl.timers[i]->timeout >= timer->timeout) {
break;
}
}
//将[j-1,using]定时器往后移
for (j = timerctl.using; j > i; j--) {
timerctl.timers[j] = timerctl.timers[j - 1];
}
timerctl.using++;
//i位置存放定时器timer
timerctl.timers[i] = timer;
timerctl.next = timerctl.timers[0]->timeout;
io_store_eflags(e);
return;
}
通过timer_settime()函数可以看出,管理定时器的结构体structTIMERCTL按照各定时器中timeout值从小到大的顺序管理定时器。所以,定时器中断程序可以提升成这样:
void inthandler20(int *esp)
{
int i;
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
timerctl.count++;
if (timerctl.next > timerctl.count) {
return; //若计数还未达到定时器中最小的目标值,则直接返回
}
for (i = 0; i < timerctl.using; i++) { //只检查正在使用的定时器[0,using)
//检测到timer的timeout还没到时,退出for循环
if (timerctl.timers[i]->timeout > timerctl.count) {
break;
}
//转换定时器的状态,向缓冲区发数据
timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
}
//定时器的数目减少i个
timerctl.using -= i;
//将[i,using)定时器往前移
for (j = 0; j < timerctl.using; j++) {
timerctl.timers[j] = timerctl.timers[i + j];
}
//next值为最小的timeout值
if (timerctl.using > 0) {
timerctl.next = timerctl.timers[0]->timeout;
} else {
timerctl.next = 0xffffffff;
}
return;
}
同(2)中的程序比较,count到达next后两程序的运行时间。(timerI-IIg)
(4) 替换数组移位处理
在多任务多应用程序同时运行都需要使用定时器时,移位处理就显得有些花时间了。定时器中断程序和timer_settime中都包含移位处理(禁止中断过程中使用的移位处理,所以也需要处理),需要对这两处的移位处理进行优化。可以用链表来替换数组的移位处理。
struct TIMER { //描述定时器的结构体
struct TIMER *next; //指向下一个定时器结构体指针
unsigned int timeout, flags; //定时值,定时器的使用状态
struct FIFO8 *fifo; //定时器管理缓冲区的结构体
unsigned char data; //定时值到达时往缓冲区存储的数据
};
struct TIMERCTL { //管理定时器的结构体
unsigned int count, next, using; //计数,记录“下一次”的定时值,正在使用的定时器的个数
struct TIMER timers0[MAX_TIMER]; //MAX_TIMER个定时器
struct TIMER *t0; //保存具有最小timeout定时器的地址
};
/* 将定时器timer添加到所有定时器的正确的位置,
* 所有的定时器按照timeout的值从小到大排列,在此过程中不允许定时器中断发生
* timer为某描述定时器的结构体的首地址
* timeout为定时器定时目标值
*/
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) {
//只有1个定时器处于运行状态
timerctl.t0 = timer;
timer->next = 0; //无下一个
timerctl.next = timer->timeout;
io_store_eflags(e);
return;
}
t = timerctl.t0;
if (timer->timeout <= t->timeout) {
//timer插在最前面的情况
timerctl.t0 = timer;
timer->next = t; //timer的下一个指向原来的具最小timeout的t0
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->next = timer;
timer->next = t;
io_store_eflags(e);
return;
}
}
//timer在最后位置
s->next = timer;
timer->next = 0;
io_store_eflags(e);
return;
}
结合timer_settime()和结构体可得出结构体中每个元素的含义。此时修改中断处理程序:
void inthandler20(int *esp)
{
int i;
struct TIMER *timer;
io_out8(PIC0_OCW2, 0x60); //继续检测PIT中断
timerctl.count++;
if (timerctl.next > timerctl.count) {
return; //若计数还未达到定时器中最小的目标值,则直接返回
}
timer = timerctl.t0; //最前面的定时器也就是具有最小timeout的赋值给timer
for (i = 0; i < timerctl.using; i++) {
//遇到未超时情况跳出for循环,链表里面保存的都是在使用的定时器
if (timer->timeout > timerctl.count) {
break;
}
//改变定时器状态
timer->flags = TIMER_FLAGS_ALLOC;
fifo8_put(timer->fifo, timer->data);
timer = timer->next; //下一个定时器
}
timerctl.using -= i;
//t0指向剩余定时器中具最小timeout的定时器
timerctl.t0 = timer;
//将具有最小timeout的定时器的timeout值付给next
if (timerctl.using > 0) {
timerctl.next = timerctl.t0->timeout;
} else {
timerctl.next = 0xffffffff;
}
return;
}
比较使用链表和数组移位程序。就在这样的环境下运行进行效率提升的程序,运行者并无太多感官体会(但这个就像改革一样,在多任务多应用中应该有明显的优势)。
Figure3. 效率提升后的定时器的运行
3.3 简洁
(1) 将被多次使用的代码块编写成函数
设计时的模块化做不到那么全面,当程序功能变得较复杂时会发现有的代码块在程序中被多次使用。这样的代码块应该被编写成函数,提升程序简洁性(当然,多一层函数调用会增添程序运行的负载,大多数情况下,这种负载的影响可以忽略不计)。如HariMain()程序中有多处使用了以下语句块:
boxfill8(buf_back, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, str);
sheet_refresh(sht_back, 32, 16, 32 + 15 * 8, 32);
可以将这3各语句块编写在一个函数内(这3各函数的参数刚好有关系):
/* 往sht图层内写字符串
* sht为要写字符串的图层
* x,y为字符串在屏幕上的起始坐标
* c为字符串的颜色
* b为字符串的背景色
* s为字符串的首地址
* l为字符串的长度
*/
void putfonts8_asc_sht(struct SHEET *sht, int x, int y, int c, int b, char *s, int l)
{
boxfill8(sht->buf, sht->bxsize, b, x, y, x + l * 8 - 1, y + 15);
putfonts8_asc(sht->buf, sht->bxsize, x, y, c, s);
sheet_refresh(sht, x, y, x + l * 8, y + 16);
return;
}
用putfonts8_asc_sht()函数替换掉HariMain()中原有的语句块。
(2) 共同使用一种数据结构体
像定时器的缓冲区,只要每个定时器往同一缓冲区内写入不同的数据就不必让每个定时器都各拥有一个缓冲区。这样还可以让多种结构体使用一个缓冲区,只要规定每种结构体所使用缓冲区数据的范围(由于缓冲区数据范围增大,所以可能需要修改管理缓冲区及缓冲区结构体的数据类型)。在这里补抄上“书”中的键盘程序。
(3) 使用哨兵简化线性表程序
//初始化PIT和定时器结构体
void init_pit(void)
{
int i;
struct TIMER *t;
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.timers0[i].flags = 0;//定时器处于未使用状态
}
t = timer_alloc(); //获取一个未使用的定时器结构体作为“哨兵”
t->timeout = 0xffffffff;
t->flags = TIMER_FLAGS_USING;
t->next = 0;
timerctl.t0 = t; //让t0指向哨兵
timerctl.next = 0xffffffff; //初始化next值为最大值,因为将用next值和各定时器timeout值比较,以获取最小的timeout值
return;
}
/* 将定时器timer添加到所有定时器的正确的位置,
* 所有的定时器按照timeout的值从小到大排列,在此过程中不允许定时器中断发生
* timer为某描述定时器的结构体的首地址
* timeout为定时器定时目标值
*/
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();
t = timerctl.t0;
if (timer->timeout <= t->timeout) {
//timer插入到线性表之前
timerctl.t0 = timer;
timer->next = t; //线性表第一个元素指向第二个元素
timerctl.next = timer->timeout;
io_store_eflags(e);
return;
}
//查找timer插入的位置
for (;;) {
s = t;
t = t->next;
if (timer->timeout <= t->timeout) {
//timer插入到s和t之间
s->next = timer;
timer->next = t;
io_store_eflags(e);
return;
}
}
}
void inthandler20(int *esp)
{
struct TIMER *timer;
io_out8(PIC0_OCW2, 0x60); //IRQ-0接收到中断信息,告知IRQ-0继续检测中断
timerctl.count++;
if (timerctl.next > timerctl.count) {
return;
}
timer = timerctl.t0; //最前面的定时器也就是具有最小timeout的赋值给timer
for (;;) {
//timers都处于运行状态
if (timer->timeout > timerctl.count) {
break;
}
//超时
timer->flags = TIMER_FLAGS_ALLOC;
fifo32_put(timer->fifo, timer->data);
timer = timer->next; //下一个定时器地址
}
timerctl.t0 = timer;
timerctl.next = timer->timeout;
return;
}
和原来相比,程序减少了结构体中计定时器个数的using变量,且使链表插入程序、中断处理程序都变得简洁了。川合秀实显示给了一个比较贴切的过程,没有直接贴出最简的代码。
4 总结
编写程序的一般过程:(通路)-->组织(设计)—>有必要就优化程序。
程序优化的前提。可以用线性表代替数组移位处理、线性表“哨兵”的用法(加入哨兵不仅可以简化程序,还可以增加程序的运行效率,见“书”中性能测试章节)。“无移位 + 哨兵”的程序在定时器数目发生变化时,运行时间不改变,这应该是编写程序的人追求的其中一个目标。
程序性能测试是启用定时器,测试某段程序前将定时器时间存放到一个变量里,程序运行结束后再看一下定时器时间,然后用后面一个时间减去之前保存的时间,就得到程序运行的时间(定时器中断程序的运行时间忽略不计的情况下)。程序性能测试分析是个高级活,需要综合的知识与分析能力。
[x86OS] Note Over.
[2015.04.17]