第13天 定时器(2)
13.1 简化字符串显示
在
HariMain函数中多次出现
boxfill8、putfonts8_asc和sheet_refresh函数,遂将该函数整合成一个函数,并改进HariMain函数。
//在sht图层中的(x,y)坐标处显示长度为l、c颜色、b背景色的字符串s
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;
}
// boxfill8的功能为将坐标(x0,y0)和(x1,y1)围成的长方形内的像素颜色变成c
// putfonts8_asc的功能为将字符串s绘制到vram中以坐标(x, y)为起点处,字符颜色为c
// sheet_refresh的功能为刷新sht图层中的坐标(bx0, by0)与(bx1-1, by1-1)围成矩形范围内的图像
13.2 重新调整FIFO缓冲区(1)
将计时器的缓冲区整合成一个,删除以前创建的timerfifo2、timerfifo3结构体变量。每次在计时器初始化时,传入的data可用于区分计时器的间隔,可以正常分辨出是哪个定时器超时了。
void HariMain(void){
struct BOOTINFO *binfo = (struct BOOTINFO*) ADR_BOOTINFO;
struct FIFO8 timerfifo;
char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8];
/* 中略 */
/* 结构体初始化 */
fifo8_init(&timerfifo, 8, timerbuf);
timer=timer_alloc();
timer_init(timer, &timerfifo, 10);/* 这里 */
timer_settime(timer, 1000);
fifo8_init(&timerfifo2, 8, timerbuf2);
timer2 = timer_alloc();
timer_init(timer2, &timerfifo2, 3);/* 这里 */
timer_settime(timer2, 300);
fifo8_init(&timerfifo3, 8, timerbuf3);
timer3 = timer_alloc();
timer_init(timer3, &timerfifo3, 1);/* 这里 */
timer_settime(timer3, 50);
/* 中略 */
for(;;){
sprintf(s, "%010d", timerctl.count);
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10);
io_cli();/*执行naskfunc.nas里的_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();
if(i == 10)/* 这里 */
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484,
"10[sec]", 7);
else if(i == 3)/* 这里 */
putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484,
"3[sec]", 6);
else if(i == 1){/* 这里 */
if(i!=0){
timer_init(timer3, &timerfifo, 0); /* 然后设置0 */
boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
}
else {
timer_init(timer3, &timerfifo, 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);
}
}
}
}
return;
}
13.3 测试性能
测试程序运行后的3s到10s期间,程序运行次数。在测试期间不移动鼠标和敲击键盘等操作。在函数 HariMain中进行调整。由于每台电脑的性能不同,输出值也是有大有小,不必在意。那么为何从3s开始测试呢,我想是因为系统在启动阶段会存在较大波动,待系统平稳阶段再进行测试,所以选择从3s后开始测试。
void HariMain(void)
{
/* 中略 */
int mx, my, i, count = 0;/* 这里 */
/* 中略 */
for (;;) {
count++;
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); /* 读出计时器的data,以分辨是哪个计时器到期 */
io_sti();
if (i == 10) {
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
sprintf(s, "%010d", count);/* 这里 */
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10);/* 这里 */
} else if (i == 3) {
putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6);
count = 0; /* 开始测试 */ /* 这里 */
} else {
/* 中略 */
}
}
}
}
}
13.4 重新调整FIFO缓冲区(2)
按照13.3中的思路,将mousefifo、mousefifo和timerfifo整合到一个结构体中。但由于以前的每个结构体中data的数据类型为char,共有256个,而将鼠标、键盘等中断均整合到一起时,char类型无法满足要求,遂将char类型改成int。按照如下分配:
0 ~ 1………………… 光标闪烁用定时器
3…………….......……3 秒定时器
10……………….....…10 秒定时器
256 ~ 511…........…… 键盘输入(从键盘控制器读入的值再加上 256 )
512 ~ 767…............… 鼠标输入(从键盘控制器读入的值再加上 512 )
下面仅展示keyboard.c函数,如mouse.c、fifo.c、bootpack.c和bootpack.h等(只要使用了FIFO结构体的函数,均需要修改)文件就不一一展示。
/* keyboard.c */
struct FIFO32 *keyfifo;
int keydata0;
//来自键盘的中断INT 0x21 编号为0x0060的设备就是键盘
void inthandler21(int *esp){
int data;
io_out8(PIC0_OCW2, 0x61);// PIC0_OCW2=0x0020 通知PIC:已经知道发生了IRQ1中断,将“0x60+IRQ号码”输出给OCW2就可以
data=io_in8(PORT_KEYDAT);// 记录按下键盘中的数据
fifo32_put(&keyfifo, data+keydata0);// 将(增加偏移量后的)数据写入在keyfifo中的位置
return;
}
// #define KEYSTA_SEND_NOTREADY 0x02
// #define PORT_KEYSTA 0x0064/* 端口状态 */
//等待键盘控制电路准备完毕
void wait_KBC_sendready(void){
/*如果键盘控制电路可以接受CPU指令了,CPU从设备号码0x0064处所读
取的数据的倒数第二位(从低位开始数的第二位)应该是0*/
for(;;){
if((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY)==0)
break;
}
return;
}
// 初始化键盘端口
void init_keyboard(struct FIFO32 *fifo, int data0){
/* 将FIFO缓冲区的信息保存到全局变量里 */
keyfifo=fifo;
keydata0=data0;//将偏移量data0写入该文件的keydata0中
/* 键盘控制器的初始化 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);//确认可否往命令端口发送指令信息
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_MODE);//向数据端口发送模式设定指令 KBC_MODE利用鼠标模式的模式号码是0x47
return;
}
13.5 加快中断处理(4)
每次中断产生时,都需要for循环进行位置移动,非常费时,而且每次timers[0]都是存储第一个将超时的计时器。如果能够采取 链表,每次遍历就不用移动赋值。这样就需要在TIMER结构体中增阿吉一个next元素,记录下一个即将超时的计时器。
/* bootpack.h */
#define MAX_TIMER 500
struct TIMER{
struct TIMER *next;// next为下一个即将超时的计时器地址 (修改处)
unsigned int timeout, flags;// timeout既定时刻 flags记录各计时器状态
struct FIFO32 *fifo;
int data;
};
struct TIMERCTL{
//count计数 next记录下一个即将到达时刻 using记录活动计时器的个数
unsigned int count, next, using;
struct TIMER *t0;// 记录第一个计时器(修改处)
struct TIMER timers0[MAX_TIMER];
};
/* timer.c */
// 设置一个计时器,就是向数组timers中添加一个数据
// 由于该数组是按照timeout从小到大进行排序的,所以就需要先确认插入的位置
void timer_settime(struct TIMER *timer, unsigned int timeout){
struct TIMER *t, *s;
timer->timeout=timeout+timerctl.count;
timer->flags=TIMER_FLAGS_USING;
int e=io_load_eflags();
io_cli();
timerctl.using++;
if(timerctl.using == 1){// 当只有当前一个计时器
timerctl.t0=timer;
timer->next=0;// 下一个计时器的地址置为空
timerctl.next=timer->timeout;// 下一个即将中断的时刻
io_store_eflags(e);
return;
}
t=timerctl.t0;
if(timer->timeout<=t->timeout){// 插在最前面,成为第一个中断计时器
timerctl.t0=timer;
timer->next=t;
timerctl.next=timer->timeout;
io_store_eflags(e);
return;
}
for(;;){
s=t;
t=t->next;
if(t==0)break;//放在链表最后
if(t->timeout>=timer->timeout){// 插入位置在链表中间,所以需要确定相邻的两个结点(插入位置在两节点中间)
s->next=timer;
timer->next=t;
io_store_eflags(e);
return;
}
}
s->next=timer;
timer->next=0;
io_store_eflags(e);
return;
}
//计时器中断,当timeout==0时则向timerctl.fifo中写入timerctl.data
void inthandler20(int *esp){
int i;
struct TIMER *timer;
io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */
timerctl.count++;
if(timerctl.next>timerctl.count)return;
// 按照链表的做法,将未超时的计时器置为链表头
timer=timerctl.t0;
for(i=0;itimeout>timerctl.count)
break;
timer->flags = TIMER_FLAGS_ALLOC;
fifo32_put(timer->fifo, timer->data);
timer=timer->next;/* 下一定时器的地址赋给timer */
}
timerctl.using-=i;
timerctl.t0=timer;/* 新移位 */
if(timerctl.using>0){//如果还有使用的计时器,则需要更新next数据,否则将next置为最大值
timerctl.next=timerctl.t0->timeout;
}else{
timerctl.next=0xffffffff;
}
return;
}
13.6 使用“哨兵”简化程序
在13.5节中使用了链表,将各中断连接起来,并按照顺序排列。链表有一个特性,就是不需要统计数量,只要从表头开始可以一直读到表尾。并在链表中添加一个哨兵,该哨兵就是防止链表不为空。若要添加哨兵,就需要修改初始化函数init_pit。还有就是using元素的重要性已经不大,如果想遍历链表,从表头就可以开始,而表头在TIMERCTL结构体中的元素t0已经记录了。
// 初始化所有计时器
void init_pit(void){
int i;
struct TIMER *t;
io_out8(PIT_CTRL, 0x34);
(中略)
for(i=0;iflags=TIMER_FLAGS_USING;
t->timeout=0xffffffff;
t->next=0;
timerctl.t0=t;//在链表中创建一个无穷大的节点,所以后续插入时只有两种可能:1.链表头2.链表中间
timerctl.next=0xffffffff;
return;
}
// 设置一个计时器,就是向数组timers中添加一个数据
// 由于该数组是按照timeout从小到大进行排序的,所以就需要先确认插入的位置
void timer_settime(struct TIMER *timer, unsigned int timeout){
(中略)
t=timerctl.t0;
if(timer->timeout<=t->timeout){// 插在最前面,成为第一个中断计时器
(中略)
}
for(;;){
(中略)
return;
}
}
return;
}
//计时器中断,当timeout==0时则向timerctl.fifo中写入timerctl.data
void inthandler20(int *esp){
(中略)
timer=timerctl.t0;
for(;;){//检查在时间点上有哪些计时器已过期,进行覆盖
(中略)
}
timerctl.t0=timer;/* 新移位 */
timerctl.next=timerctl.t0->timeout;
return;
}