自制操作系统日志——第十二天
从今天开始,我们将花费两天的时间来进行计算机中定时器的制作。有了定时器后,才能够为程序和cpu更加便利的进行计时。可能会稍难一些了!!! 做好准备,冲!!!!
文章目录
- 自制操作系统日志——第十二天
- 一、初识定时器
- 二、设定多个计时器,并优化中断程序
- 总结
一、初识定时器
定时器,就是指每隔一段时间就会发送一个中断信号给cpu。正因,有了定时器,cpu才不用花费额外的功夫去计量时间!
如果cou不使用计时器的话,那么cpu如果需要计算时间,只能怪依靠记住每一条指令的执行时间了。例如,向寄存器写入常数的mov 需要一个时钟周期,而某某函数又需要105个时钟周期等等。而这里的时钟周期,又并非是一个统一的固定值,例如cpu的主频100Mhz,一个始时钟周期是10纳秒;如果cpu主频是200MHZ 则一个时钟周期是5纳秒。。。
因此,即使使用cpu计量时间的话,倘若某个程序中时间计量出错,则就会导致需要使用时间的程序(例如:记录时间的电子表)变快或者变慢;且也不能使用hlt指令了,因为一旦cpu休眠就无法为程序计算时间了!!!
因此,我们需要进行管理定时器。至于如何管理呢? 我们只需对PIT(可编程的间隔型定时器)进行设定即可,而PIT又与中断管理表PIC的IRQ-0相连接。因此,我们只需增加对应的中断处理程序即可:使用的定时器编号应该是8254芯片。
PIT的设定规则:
- AL = 0x34; OUT(0x43, AL)
- AL=中断周期的低8位; OUT(0x40, AL)
- AL=中断周期的高8位; OUT(0x40,AL)
这里,实际中断产生的频率 = 主频 / 设定数。
对于我们这次模拟的来说,如果设定值是1000 则频率为1.19318khz ;如果是11932的话 则为 100hz 约为10ms。
以下开始编写对应的程序:
timer.c
//定时器
#include "bootpack.h"#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
//初始化,将中断周期设定为11932=0x2e9c;则实际中断频率= 主频/设定数 = 100HZ ;(这里主频约为11931800左右)
void init_pit(void)
{io_out8(PIT_CTRL, 0X34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);return;
}void inthandler20(int *esp)
{io_out8(PIC0_OCW2, 0x60);//IRQ-0信号接收完后告知PICreturn;
}
naskfunc.nas:
_asm_inthandler20:push espush dsPUSHADmov EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler20POP EAXPOPADPOP DSPOP ESIRETD
dsctbl.c
set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 << 3, AR_INTGATE32);set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 << 3, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 << 3, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 << 3, AR_INTGATE32);
主函数:
init_pit();io_out8(PIC0_IMR, 0xf8); /* 开放PIC1和键盘中断(11111000),键盘是IRQ1 */io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) ,鼠标是IRQ12*/
make run 即可!!!
这里为了进一步的展示计时器的功能,我们制作一个计时器:
Bootpack.h:
//timer.c
struct TIMERCTL
{unsigned int count;
};
extern struct TIMERCTL timerctl;
void init_pit(void);
void inthandler20(int *esp);
timer.c:
struct TIMERCTL timerctl;#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
//初始化,将中断周期设定为11932=0x2e9c;则实际中断频率= 主频/设定数 = 100HZ ;(这里主频约为11931800左右)
void init_pit(void)
{io_out8(PIT_CTRL, 0X34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;return;
}void inthandler20(int *esp)
{io_out8(PIC0_OCW2, 0x60);//IRQ-0信号接收完后告知PICtimerctl.count++;return;
}
主函数:
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);
make run一下后,可以看出大约每秒增加100:
进一步的,我们就可以利用定时器做一个基准测试程序,用于计算某个程序耗时多久。但是,接下来,先让我们做一下超时的功能试试手:
首先,在bootpack.c中添加如下与超时有关的信息:
struct TIMERCTL
{unsigned int count;unsigned int timeout; //用这个记录离超时还要多久时间,一旦变为了0,就向缓冲区发送数据struct FIFO8 *fifo;unsigned char data;
};
然后让我们修改一下timer.c函数吧:
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);//IRQ-0信号接收完后告知PICtimerctl.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(); //此时还没有完全结束IRQ0因此,如果此时又中断进入会引起混乱的!!timerctl.timeout = timeout;timerctl.fifo = fifo;timerctl.data = data;io_store_eflags(eflags);return;
}
然后再改一下主函数部分:
struct FIFO8 timerfifo;char s[40], keybuf[32], mousebuf[128], timebuf[8];略fifo8_init(&timerfifo, 8, timebuf);settimer(1000, &timerfifo, 1);init_keyboard();enable_mouse(&mdec); 略for(;;) {略io_cli(); //IF=0if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 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发送一次信息,因此我们可以再约1000时候看到信息的显现:
二、设定多个计时器,并优化中断程序
为方便同时给多个程序进行即使准备,我们将建立起多个计时器。
这里,我们再设置多个计时器的过程中,为减少后续在此中断过程中所用的时间,我们将参照图层那一部分的构造进行设计。首先,我们来涉及一下结构体变量:
bootpack.h:
#define MAX_TIMER 500
struct TIMER
{unsigned int timeout, flags;//flags表示各个定时器的状态; timeout的含义,这里指予定时刻,通过settime中赋予的超时时间+当前时刻来计算,当到达啥时刻时算超时struct FIFO8 *fifo;//用于将超时的信息传给缓冲区。unsigned char data;
};
struct TIMERCTL
{unsigned int count, next, using;//using用于记录现在有几个定时器处于活动中struct TIMER *timers[MAX_TIMER];struct TIMER timers0[MAX_TIMER];
};
这里,多设计一个timers,用于将计时器按照到达时刻的先后顺序进行排序。然后再多加上一个next的参数,用于指向下一个超时的时刻,以便加快非超时时的判断过程。
然后修改timer.c:
首先进行修改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;//因此最初没有正在运行的定时器timerctl.using = 0;for(i = 0; i < MAX_TIMER; i++){timerctl.timers0[i].flags = 0; //未使用}return;
}
然后,修改alloc函数,将已分配的计时器存入再timers0当中,并准备后续的timers排序做准备:
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;//没找到
}
然后,进行修改settime,设定timers中的计时器,按照到达时刻的顺序的先后进行排序,到达时刻越晚的排在越后面:
//主程序中对于pit计时器是先注册并设置完一个之后再弄另一个的!!
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;
}
设定中断处理程序:
void inthandler20(int *esp)
{int i, j;io_out8(PIC0_OCW2, 0x60);//IRQ-0信号接收完后告知PICtimerctl.count++;if(timerctl.next > timerctl.count){return; //还不到下一个时刻,因此返回}for (i = 0; i < timerctl.using; i++) {// timers的定时器都是活动中的因此不需要确认flagsif(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;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 = 0xfffffff;}return;
}
然后,我们继续修改一下主程序,实现三个计时器的功能,并且实现光标的闪烁!
void HariMain(void)
{//鼠标键盘,bootinfo等等的定义struct BOOTINFO *binfo = ( struct BOOTINFO *) ADR_BOOTINFO;struct FIFO8 timerfifo, timerfifo2, timerfifo3;char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8];struct TIMER *timer, *timer2, *timer3;略:io_out8(PIC0_IMR, 0xf8); /* 开放PIC1和键盘中断(11111000),键盘是IRQ1 */io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) ,鼠标是IRQ12*/fifo8_init(&timerfifo, 8, timerbuf);timer = timer_alloc();timer_init(timer, &timerfifo, 1);timer_settime(timer, 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(;;) {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);io_cli(); //IF=0if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo)+ fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 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); //然后设置为0boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);} else {timer_init(timer3, &timerfifo3, 1); //然后设置为1boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);}timer_settime(timer3, 50);sheet_refresh(sht_back, 8, 96, 16, 112);} }}
}
以上就完成了对应的程序,让我们make run一下:
总结
至此,我们完成了计时器的第一部分,还有一点点需要继续优化的地方,明天加油吧!