harib09a:
定时器:(Timer)每隔一段时间,会向CPU发送一个中断。这样CPU不用记住每一条指令的执行时间。没有定时器不少指令CPU都很难执行。例如HLT指令,这个指令的执行时间不是个固定值,没有定时器,CPU就不能执行这个指令
-PIT-: 可编程的间隔型定时器(Programmable Interval Timer )经过设定PIT,可让定时器每隔必定时间就产生一次中断。PIT和PIC都被集成在别的芯片里了,链接着IRQ的0号中断,IRQ0的中断变动周期是经过寄存器AL的值来设定的。编程
这里笔者把中断频率设定为100HZ,(1秒钟产生100次中断)根据笔者CPU的主频,计算获得AL为0x2e9c(11932)数组
一、初始化定时器Timer( 三次调用OUT() )缓存
//定时器初始化函数init_pit //timer.c节选 #define PIT_CTRL 0x0043 #define PIT_CNT0 0x0040 void init_pit(void) { //中断周期的变动规则(三次调用OUT()):P222有规则介绍 //至于规则为何是这样连续调用三次OUT();笔者介绍说这个是由芯片设定决定的。咱们就不深究;知道这样的调用规则就行。 io_out8(PIT_CTRL, 0x34);//第一步:调用OUT(0x43,AL);AL=0x34 此时AL为定值PIT_CTRL(0x34) io_out8(PIT_CNT0, 0x9c);//第二步:调用OTU(0x40,AL);AL=0x9c 此时AL为中断周期的低八位(0x9c) io_out8(PIT_CNT0, 0x2e);//第三步:调用OTU(0x40,AL);AL=0x2e 此时AL为中断周期的高八位 (0x2e) return; }
二、在HariMain中调用上面的定时器初始化函数函数
//bootpack.c节选 void HariMain(void) { //............... fifo8_init(&keyfifo, 32, keybuf); fifo8_init(&mousefifo, 128, mousebuf); init_pit(); //在这里调用定时器初始化函数 io_out8(PIC0_IMR, 0xf8); //PIT和PIC1和键盘中断设置1111-1000 io_out8(PIC1_IMR, 0xef); //PIC1 设置鼠标的中断设置1110-1111 //............. }
三、编写IRQ0发生时的中断处理程序spa
这里咱们先来解释一下io_out8():io_out,表示芯片的端口。例如这里的io_out8(PIC0_OCW2,0x60);8表示这个端口或者寄存器是8位的,参数的第一个表哪一个芯片的哪个寄存器,固然这个寄存器位数要和前面的8位数同样。第二个参数表示要设置的值,位数也要对应。例如:调用io_outM(A_B,N) .表示把芯片A中的B寄存器(这个寄存器的位数为M)的值设置为N 。io_out8(PIC0_OCW2,0x60)表示的意思就是把芯片PIC0的OCW2寄存器(8位)的值设置为0x60。操作系统
void inthandler20(int *esp) { //把芯片PIC0的OCW2寄存器(8位)的值设置为0x60 io_out8(PIC0_OCW2, 0x60); /* IRQ-00信号接收完了的信息通知给PIC */ return; } //naskfunc.nas //函数_asm_inthandler20 //20中断号,这里把定时器的中断号设置为20
四、把定时器的中断程序注册到IDT中指针
void init_gdtidt(void) { //................... //20号为定时器的中断 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); }
harib09b:
上面咱们已经对定时器作了一些准备工做,下面咱们来试一试让他运行起来,咱们定义了一个struct TIMERCTL结构体,在结构体中定义了一个计数变量count,在PIT初始化的时候,把count初始化为0,而后在定时器中断程序中,不断对count进行自加,所以定时器每发生一次中断,count都会增长1,这样用来记录定时器中断发生的次数。最后把count显示在咱们前面实现的窗口中。code
/* timer.c */ //计数结构体定义 struct TIMERCTL { unsigned int count; }; struct TIMERCTL timerctl; void init_pit(void) { //..初始化count为 0 ............. timerctl.count = 0; return; } void inthandler20(int *esp) { //定时器每次发生中断会调用这个函数 //这是计数变量count会 +1 。 timerctl.count++; return; } //在HariMain中把数值显示出来:HariMain节选 void HariMain(void) { for (;;) { //先把值写到字符串s中 sprintf(s, "%010d", timerctl.count); //初始化窗口图层的缓存buf_win boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); //把字符串写到窗口图层缓存buf_win中 putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); //刷新并显示窗口图层sht_win sheet_refresh(sht_win, 40, 28, 120, 44); //....... } }
harib09c:
超 时:笔者举了一个很形象的例子来讲明什么是超时:“喂,OS小弟,10秒钟以后,通知我一声,我要干什么干什么”咱们就把这样的功能叫作超时(定时吧)。下面咱们来实现这个功能。对象
一、扩展struct TIMERCTL记录超时有关的信息blog
/* timer.c */ struct TIMERCTL { unsigned int count;//定时器中断计数器 unsigned int timeout; //记录离超时还有多长时间 struct FIFO8 *fifo;//使用FIFO缓冲区来通知 unsigned char data; };
二、修改PIT初始化和中断处理函数
void init_pit(void) { //.........修改pit初始化函数 //将count和timeout都初始化为0 timerctl.count = 0; timerctl.timeout = 0; return; } void inthandler20(int *esp) { //.........修改第20号中断 if (timerctl.timeout > 0) { /* 若是已经设定了超时 */ timerctl.timeout--;//没发生一次中断,记录离超时的时间timeout减1 if (timerctl.timeout == 0) { //若是记录的时间已经没有了,说明已经到了定时的时间 fifo8_put(timerctl.fifo, timerctl.data);//时间已经到了,经过FIFO缓冲区通知CPU } } return; } //定时函数:进行超时设定 void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data) { int eflags; //先禁止中断,避免IRQ0中断没有结束前进来中断发生混乱 eflags = io_load_eflags(); io_cli(); //接着进行超时设置。 timerctl.timeout = timeout; timerctl.fifo = fifo; timerctl.data = data; //最后恢复中断状态 io_store_eflags(eflags); return; }
三、在HariMain中调用定时函数settimer()
//在定时器的中断函数发生1000次中断后,向timerFIFO中写入”1“,而timerFIFO接收到数据,就会砸屏幕上显示”10[sec]“ settimer(1000, &timerfifo, 1);
harib09d:
上一步咱们实现了超时的功能。在操做系统中,超时功能的使用很是方便也很是多。下面咱们来根据超时设定多个定时器
一、修改结构体struct TIMERCTL
#define MAX_TIMER 500 //定时器结构体 struct TIMER { //timeout:定时器的中断次数 //flag:记录各个定时器的状态 unsigned int timeout, flags; struct FIFO8 *fifo; unsigned char data; }; struct TIMERCTL { unsigned int count; struct TIMER timer[MAX_TIMER]; };
二、修改timer.c中的相关函数
#define TIMER_FLAGS_ALLOC 1 /* 表示定时器已配置 */ #define TIMER_FLAGS_USING 2 /* 定时器运行中 */ void init_pit(void) { //.................... for (i = 0; i < MAX_TIMER; i++) { //初始化TIMERCTL结构体将全部定时器标志位置0,表示未使用 timerctl.timer[i].flags = 0; }//................... } struct TIMER *timer_alloc(void) { //... if (timerctl.timer[i].flags == 0) { //定时器已分配,将FLAG由未使用改成已配置 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; return &timerctl.timer[i]; }//... } void timer_free(struct TIMER *timer) { timer->flags = 0; /* 定时器释放后,将flag置0 */ } void timer_settime(struct TIMER *timer, unsigned int timeout) { //... timer->flags = TIMER_FLAGS_USING;//设置后,flag置运行中 } void inthandler20(int *esp) { //... if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { timerctl.timer[i].timeout--; //定时器运行中,每次20号中断一次,timeout-1 if (timerctl.timer[i].timeout == 0) { //timeout没有了 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; //解除运行状态, fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);//通知CPU想FIFO中写入data } } }
三、HariMain中设置3s和10s两个定时器
if (fifo8_status(&timerfifo) != 0) { //设置10s定时器 i = fifo8_get(&timerfifo); /* 首先得到第一个FIFO缓冲区的地址 */ io_sti(); //IDT/PIC开始向CPU发送中断信号 //显示出来 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) {//设置3s定时器 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); //得到第三个FIFO缓冲区的地址 io_sti(); if (i != 0) { //i!=0 表示FIFO地址获取成功。 timer_init(timer3, &timerfifo3, 0); /* 初始化定时器timer3,flag=0,表示没使用 */ boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111); } else { timer_init(timer3, &timerfifo3, 1); /* 初始化timer3,flag=1表示该定时器一杯分配 */ boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111); } timer_settime(timer3, 50); //设置定时的时间,50个中断一次闪烁 sheet_refresh(sht_back, 8, 96, 16, 112); }
harib09e:
加快中断处理(01):咱们知道,中断在CPU中基本是时刻都在发生的,若是按照上面的中断速度,CPU基本什么都作不了。接下来咱们加快定时器中断处理的速度。在上面程序中,为了计时,咱们在每一次中断发生时,让定时器的timeout减1。这样会增长中断函数执行的时间。修改timeout的含义,表示予定时间,接下来用count和timeout比较就知道是否达到定时的时间了。
void inthandler20(int *esp) { //... if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { //当予定时间小于计数count的时间时,就把数据写到FIFO中。 //避免了timeout--的操做 if (timerctl.timer[i].timeout <= timerctl.count) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } }//.... } void timer_settime(struct TIMER *timer, unsigned int timeout) { //由于定时器启动时,不能肯定count的值,因此予定时间设定为count+定时时间 timer->timeout = timeout + timerctl.count; //此时定时器启动了,FLAG设置定时器正在使用 timer->flags = TIMER_FLAGS_USING; return; } //时刻调整:cout从系统启动,到0xffff_ffff的时间为497天。 //每一年多一点的时间启动一次。须要从新将count的值置为0
harib09f:
加快中断处理(02):咱们发现中断执行if(i<MAX_TIMER)的次数太多了。并且大多数是没必要要的。追加一个变量timerctl.next记住下一个时刻的值,这样不用每次都作无用的判断。
struct TIMERCTL { //next记录下一个时刻的值 unsigned int count, next; struct TIMER timer[MAX_TIMER]; }; //把中断函数修改一下,把使用到了next的地方都修改一下 void inthandler20(int *esp) { int i; io_out8(PIC0_OCW2, 0x60); /* IRQ-00信号接收结束的信息通知给PIC */ 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) { /* 定时时间已经到了,写FIFO */ 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) { //... timerctl.next = 0xffffffff; /* 刚初始化,没有在运行的定时器 */ for (i = 0; i < MAX_TIMER; i++) { timerctl.timer[i].flags = 0; /* 没有使用 */ }//... } void timer_settime(struct TIMER *timer, unsigned int timeout) { //... if (timerctl.next > timer->timeout) { /* 此时刚启动定时器,初始化next在timeout的下一个时刻 */ timerctl.next = timer->timeout; }//... }
harib09g:
加速中断处理(03):到达next和没有到达next时刻的定时器处理的时间差异很大。咱们进一步改进:定义timers[]用来存放按照顺序排列的定时器的地址(和以前描绘图层的处理顺序很类似)
struct TIMERCTL { unsigned int count, next, using;//using记录出处于活动中的定时器数量 struct TIMER *timers[MAX_TIMER];//定时器指针数组:存放排好序的定时器地址 struct TIMER timers0[MAX_TIMER];//定时器结构体数组:存放定时器 };
接下来修改中断,初始化,定时器初始化,定时器分配函数(每次修改了一点点,要贴这么多代码。还要重复写这么多注释!!)
void inthandler20(int *esp) { int i, j; io_out8(PIC0_OCW2, 0x60); /* IRQ-00通知PIC接收结束信息 */ timerctl.count++;//每一次中断,计数器count++ if (timerctl.next > timerctl.count) { return;//还不到下一个时刻,结束 } for (i = 0; i < timerctl.using; i++) { /* 这里直接用using。只对使用中的定时器进行定时器地址排序 */ //timeout>count,没有超时 if (timerctl.timers[i]->timeout > timerctl.count) { break; } /* 超时了,先设置标志位已分配 */ timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC; //写data到FIFO中 fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data); } /* 上面循环超时执行后到这里来了 这是i保存的是超时寄存器的个数 */ timerctl.using -= i; //超时了i个定时器,活动中的定时器减小i个 for (j = 0; j < timerctl.using; j++) { //对于每个活动中的定时器 //这是活动中的定时器已经减小了i个,须要对活动中的定时器从新排序,地址从新赋值给timers[] timerctl.timers[j] = timerctl.timers[i + j]; } if (timerctl.using > 0) { //活动中的定时器不为0 //将next指向下一个时刻 timerctl.next = timerctl.timers[0]->timeout; } else { //不然,没有活动中的定时器 timerctl.next = 0xffffffff; } return; } void init_pit(void) { //...定时器初始化函数,此时刚刚初始化 timerctl.next = 0xffffffff; /* 没有活动中的定时器 */ timerctl.using = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timers0[i].flags = 0; /* 刚初始化,flag都为0 */ } return; } void timer_settime(struct TIMER *timer, unsigned int timeout) { //...设置定时器,(至关于建立了一个对象的一个实例) for (i = 0; i < timerctl.using; i++) { //这里从排序好的定时器的地址开始找找到最后一个定时器地址的位置i if (timerctl.timers[i]->timeout >= timer->timeout) { break; } } for (j = timerctl.using; j > i; j--) { //在地址i的后面,把新的(实例)定时器的地址放到i的后面 timerctl.timers[j] = timerctl.timers[j - 1]; } timerctl.using++; //有了新的定时器,活动定时器数量增长一个 timerctl.timers[i] = timer; //赋值,定时的时间 timerctl.next = timerctl.timers[0]->timeout;//新的活动定时器来了,next指向下一个时刻 io_store_eflags(e); //恢复中断。在前面部分是先禁止了全部的中断的,io_load_eflags() return; }