自制操作系统日志——第十二天

自制操作系统日志——第十二天

从今天开始,我们将花费两天的时间来进行计算机中定时器的制作。有了定时器后,才能够为程序和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一下:
在这里插入图片描述
在这里插入图片描述


总结

至此,我们完成了计时器的第一部分,还有一点点需要继续优化的地方,明天加油吧!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值