30天自制操作系统(第13天)

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

你可能感兴趣的:(其他)