自制操作系统日志——第十二天
从今天开始,我们将花费两天的时间来进行计算机中定时器的制作。有了定时器后,才能够为程序和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信号接收完后告知PIC
return;
}
naskfunc.nas:
_asm_inthandler20:
push es
push ds
PUSHAD
mov EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler20
POP EAX
POPAD
POP DS
POP ES
IRETD
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信号接收完后告知PIC
timerctl.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信号接收完后告知PIC
timerctl.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=0
if (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信号接收完后告知PIC
timerctl.count++;
if(timerctl.next > timerctl.count)
{
return; //还不到下一个时刻,因此返回
}
for (i = 0; i < timerctl.using; i++) {// timers的定时器都是活动中的因此不需要确认flags
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;
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=0
if (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); //然后设置为0
boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
} else {
timer_init(timer3, &timerfifo3, 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);
}
}
}
}
以上就完成了对应的程序,让我们make run一下:
总结
至此,我们完成了计时器的第一部分,还有一点点需要继续优化的地方,明天加油吧!