【操作系统】30天自制操作系统--(11)定时器1

        本章介绍定时器中断的使用。

一 定时器中断的使用

        由【操作系统】30天自制操作系统--(5)分割编译与中断处理中的表格可以看到,对于定时器中断的使用就用到了IRQ0这个中断请求。

中断请求硬件设备
IRQ0系统计时器
IRQ1键盘
IRQ2可设置中断控制卡
IRQ3COM2(串行接口2)
IRQ4COM1(串行接口1)
IRQ5
IRQ6磁盘机
IRQ7并行接口
IRQ8CMOS/时钟
IRQ9
IRQ10
IRQ11
IRQ12PS/2鼠标
IRQ13算术处理器(Arithmetic Processor)
IRQ14Primary(主)IDE控制器
IRQ15Secondary(从)IDE控制器

        其中断周期的变更步骤如下:

        这边用的是8254定时器芯片 ,其主频为1.19318MHz(参考8254芯片详解

        就是说,如果想要IRQ0中断时间为10ms,就需要往寄存器中写值11932(0x2e9c):

#include "bootpack.h"

#define PIT_CTRL    0x0043
#define PIT_CNT0    0x0040

void init_pit(void) {
    io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	return;
}

        在主函数中调用(下面关于中断屏蔽寄存器IMR的使用可以参考【操作系统】30天自制操作系统--(5)分割编译与中断处理):

void HariMain(void)
{
    // ...
    init_pit();
    // ...
    io_out8(PIC0_IMR, 0xf8);  /* PIT和PIC1和键盘设置为许可(11111000) */
    io_out8(PIC1_IMR, 0xef);  /* 鼠标设置为许可(11101111) */
    // ...
}

        当然,编写中断服务程序---->汇编实现中断服务程序的返回---->将编写好的中断处理程序(包含中断服务程序和汇编处理两部分)注册到IDT中断记录表中 这几个步骤也与鼠标和键盘中断的处理如出一辙,这边不再赘述,只简单将这几处程序贴在一起:

void inthandler20(int *esp) {
    io_out8(PIC0_OCW2, 0x60);  /* 把IRQ-00信号接收完了的信息通知给PIC */
    /* 暂时什么都不做 */
    return;
}
_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
void init_gdtidt(void)
{
    /* 中略 */

    /* 中断注册表IDT注册 */
    set_gatedesc(idt + 0x20, (int)asm_inthandler20, 2*8, AR_INTGATE32); //IRQ0定时中断
	set_gatedesc(idt + 0x21, (int)asm_inthandler21, 2*8, AR_INTGATE32); //IRQ1键盘中断
	set_gatedesc(idt + 0x27, (int)asm_inthandler27, 2*8, AR_INTGATE32); //IRQ7并行接口
	set_gatedesc(idt + 0x2c, (int)asm_inthandler2c, 2*8, AR_INTGATE32); //IR12鼠标中断
}

        至此,完成了定时器中断处理程序的编写和注册。接下来就是往服务程序里面填写逻辑了。

二 添加定时器功能(计数器、超时返回、设置多个定时器)

        这边列出了三种功能的使用。

【1】计数器:很简单,定时器中断服务程序中整个变量自加,完了在窗口中显示出来。

【2】超时返回:

        (1)首先向结构体struct TIMERCTL中添加代码记录超时的信息:

struct TIMERCTL {
	unsigned int count;
	unsigned int timeout;  /* 距离暂停还有多少时间 */
	struct FIFO8 *fifo;    /* 接收到时间后程序发送数据的缓冲区 */
	unsigned char data;
};

        (2)随后在中断服务程序里面实现的暂停功能,在settimer中 ,为了防止IRQ0中断乱入,采取的是禁止中断 + 处理 + 开放 的策略(这一点之前鼠标键盘也是这么处理的)。

struct TIMERCTL timerctl;

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-00信号接收完了的信息通知给PIC */
    timerctl.count++;
    if (timerctl.timeout > 0) {  /* 如果设定了超时 */
        timerctl.timeout--;
        if (timerctl.timeout == 0) {
            fifo8_put(timerctl.fifo, timerctl.data);
        }
    }
    return;
}

void settimmer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data){
    int eflags;
    eflags = io_load_eflags();
    timerctl.timeout = timeout;
    timerctl.fifo = fifo;
    timerctl.data = data;
    io_store_eflags(eflags);
    return;
}

        (3)最后,在主函数中读取定时器缓冲数组中的数据(如果有的话),并在屏幕上返回一个定时器超时的信息“10[sec]”:

void HariMain(void)
{
    //(中略)
    struct FIFO8 timerfifo;
    char s[40], keybuf[32], mousebuf[128], timerbuf[8];
    //(中略)
    fifo8_init(&timerfifo, 8, timerbuf);
    settimer(1000, &timerfifo, 1);
    (中略)
    for (;;) {
        //(中略)
        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();
		        putfonts8_asc(buf_back, binfo-
		        >scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
		        sheet_refresh(sht_back, 0, 64, 56, 80);
	        }
        }
    }
}

【3】设置多个定时器:这边类似于之前【操作系统】30天自制操作系统--(8)内存管理中“内存管理表”的方法,添加一个flags标志位,用来标识定时器的状态(包括未使用为0,已配置为1,运行中为2)

        (1)首先修改结构体struct TIMERCTL(这边最大可以配置500个定时器):

#define MAX_TIMER		500
struct TIMER {
	unsigned int timeout, flags;
	struct FIFO8 *fifo;
	unsigned char data;
};
struct TIMERCTL {
	unsigned int count;
	struct TIMER timer[MAX_TIMER];
};

        (2)添加一些定时器的配置和释放等功能函数(即对flags标志位进行操作):

#include "bootpack.h"

#define PIT_CTRL    0x0043
#define PIT_CNT0    0x0040

struct TIMERCTL timerctl;

#define TIMER_FLAGS_ALLOC		1	/* 已配置状态 */
#define TIMER_FLAGS_USING		2	/* 定时器运行中 */

void init_pit(void) {
    io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
    timerctl.count = 0;
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
        timerctl.timer[i].flags = 0;  /* 未使用 */
    }
	return;
}

/**
 * @brief 返回未在使用的 timer
 */
struct TIMER *timer_alloc(void){
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
        if (timerctl.timer[i].flags == 0) {
            timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
            return &timerctl.timer[i];
        }
    }
    return 0;
}


void timer_free(struct TIMER *timer){
    timer->flags = 0;
    return;
}

void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data){
    timer->fifo = fifo;
    timer->data = data;
    return;
}

void timer_settime(struct TIMER *timer, unsigned int timeout){
    timer->timeout = timeout;
    timer->flags = TIMER_FLAGS_USING;
    return;
}

void inthandler20(int *esp){
    io_out8(PIC0_OCW2, 0x60);  /* 把IRQ-00信号接收结束的信息通知给PIC */
    timerctl.count++;
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
        if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			timerctl.timer[i].timeout--;
			if (timerctl.timer[i].timeout == 0) {
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			}
		}
    }
    return;
}

        (3)在主函数中调用(这边作者配置了三个定时器,一个是10s超时显示字符串;一个是3s超时显示字符串;一个是每隔0.5s通过切换定时器的使用状态来控制屏幕上小矩形光标的亮灭,以实现光标闪烁的效果)。这边主函数的代码比较简单但冗长,就不贴出来了。

三 加快定时器中断处理

        这边列出了三种方法。

【1】原本是timeout参数自减,根据其是否到0来判断超时,现在修改为count参数直接跟timeout参数相比较,减少一行timeout参数的运算。算是一个小优化吧,具体程序略。

【2】在结构体中新增next参数用来记住下一个时间,先和 timeout 最小的作比较。这样就不需要每次都要比较判断所有的定时器。具体程序略。

【3】参考【操作系统】30天自制操作系统--(9)叠加处理中对于图层的处理,专门开辟一块内存用来存放某种顺序排列好的定时器地址struct TIMER *timer[MAX_TIMER],以减少到达next时刻的处理时间(具体程序略。个人感觉方法三,与增加的代码复杂度相比,带来的性能提升感觉不大):

struct TIMERCTL {
	unsigned int count, next, using;
	struct TIMER *timers[MAX_TIMER];
	struct TIMER timers0[MAX_TIMER];
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值