MacOS环境-手写操作系统-25-实现定时器

简介: MacOS环境-手写操作系统-25-实现定时器

实现定时器

1.简介

当指定的时间过去后 系统会触发你给定的回调函数


Timer功能实在是太重要了 如果没有定时器 操作系统很多任务都做不了


至少你编程画个时钟 搞个闹钟程序什么的 你就没法实现


从这节开始 我们看看timer功能是怎么实现的


2.代码

主控制器的IRQ0对应的就是时钟中断


只要我们做好相关配置 那么在指定间隔内 IRQ0导线就会像CPU发送中断信号


首先我们代码要做的是初始化8259A时 打开这一中断功能


在内核实现的汇编部分做如下修改(kernel.asm)


init8259A:
....
mov  al, 11111000b ;允许键盘和时钟中断
     out  021h, al
     call io_delay
....

注意看 三个0表示打开主8259A的IRQ0 IRQ1 IRQ2三根信号线


IRQ0对应的就是时钟中断 打开IRQ0后 当中断信号发送到CPU时


我们需要CPU调用我们提供的中断程序 因此还需做以下修改


LABEL_IDT:
%rep  32
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep

;响应时钟中断的中断描述符
.020h:
    Gate SelectorCode32, timerHandler,0, DA_386IGate

.021h:
    Gate SelectorCode32, KeyBoardHandler,0, DA_386IGate

%rep  10
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep

.2CH:
    Gate SelectorCode32, mouseHandler,0, DA_386IGate

我们在中断向量表中 增加了一个用于调用时钟中断的描述符 该描述符对应的中断调用 名字叫timerHandler

我们继续看timerHandler的实现

_timerHandler:
timerHandler equ _timerHandler - $$
     push es
     push ds
     pushad
     mov  eax, esp
     push eax
     
     call intHandlerForTimer
     
     pop  eax
     mov  esp, eax
     popad
     pop  ds
     pop  es
     iretd

这段代码的实现跟以前的中断处理方法一样 先把寄存器压到堆栈上保存起来


然后调用C语言实现的中断处理函数intHandlerForTimer


在查看C语言对intHandlerForTimer的实现之前 我们需要搞清楚的是


如何配置时钟中断 使其在一秒内发生几次中断合适?


我们这里暂定1秒内产生100次中断 这种中断频率足以满足我们系统开发需求


于是我们需要做相应配置 配置的方法是 像8259A芯片的对应端口发送指定数据


首先需要向端口0x43发送一个数值0x34 紧接着向端口0x40发送两个数据0x9c,0x2d


这样时钟中断就能在1秒内发生100次了


配置代码如下(Timer.h)

#define PIT_CTRL   0x0043
#define PIT_CNT0   0x0040


void init_pit(void);

struct TIMERCTL {
    unsigned int count;
    unsigned int timeout;
    struct FIFO8 *fifo;
    unsigned char data;
};

struct TIMERCTL* getTimerController();

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data);

init_pit(void) 就是用来实现时钟中断配置的


它的作用是向指定端口发送指定数据 TIMERCTL 数据结构叫时钟管理器


其中的count用来记录时钟中断发送了多少次


timeout用来计时 一旦timeout为0


管理器将触发指定动作 其他的数据在后面我们会详细解释


时钟中断的初始化实现


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;
}


初始化就如同前面我们所说的 向指定端口发送一些指定数据而已


设置好中断机制后 我们可以实现超时功能 也就是通过时钟管理器设置一个时间片


一旦时间片结束后 让时钟管理器触发我们提供的一个函数


这个时间片的大小对应的就是TIMECTRL结构体里面的timeout


C语言模块是怎么响应中断信号的


void intHandlerForTimer(char *esp) {
    io_out8(PIC0_OCW2, 0x60);
    timerctl.count++;
    if (timerctl.timeout > 0) {
        timerctl.timeout--;
        if (timerctl.timeout == 0) {
            fifo8_put(timerctl.fifo, timerctl.data);
        }
    }
    return;
}


每次响应中断信号时 先向8259A发送一个命令 命令的数值是0x60


要求8259A下次继续发送中断信号 如果不这么做 下次芯片就不给我们发送信号了


然后把时钟管理器的count计数加一 对应中断响应的次数


每次中断发送 我们都把时间片对应的数值减一


如果时间片减少到0 表明超时 此时向时钟管理器附带的FIFO队列写入一个数据


FIFO队列我们在实现鼠标响应的章节介绍过


这个队列的作用主要用来通知内核 超时发生了在内核的主循环里面会不停的监控时钟管理器这个队列是否为空


如果是空 那么内核就认为没有超时事件发生


如果队列里面有数据 那表明超时事件发生了


怎么设定超时对应的时间片呢?


我们在时钟管理器的实现中 增加一个settimer函数


void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data) {
    int eflags;
    eflags = io_load_eflags();
    io_cli();//暂时停止接收中断信号
    timerctl.timeout = timeout;//设定时间片
    timerctl.fifo = fifo;//设定数据队列,内核在主循环中将监控这个队列
    timerctl.data = data;
    io_store_eflags(eflags);//恢复接收中断信号
    return;
}
struct TIMERCTL* getTimerController() {
    return &timerctl;
}


io_load_eflags() 和 io_store_eflags() 这两个函数我们以前提到过


CPU会根据一系列状态信息来调整自己的运行状况


例如当前中断功能是否打开 是否有计算溢出等等


这些状态信息都存储在指定寄存器的指定比特位中(CPU上的相关硬件)


io_load_eflags() 就是获取这些信息 把他们存储到变量eflags中


io_store_eflags(eflags) 是把前面存储的状态信息重新设置回去


io_cli()的作用是让CPU停止接收一切中断信号 也就是设置对应的比特位让CPU运行时忽略到来的中断请求


io_store_eflags作用就是重新恢复原来状态 让CPU重新接收中断信号


内核主循环的处理


....
static struct FIFO8 timerinfo;
static char timerbuf[8];
....
void CMain(void) {
    ....
    init_pit();
    fifo8_init(&timerinfo, 8, timerbuf);
    settimer(500, &timerinfo, 1);
    ....
}


内核启动时 初始化时钟控制器 并初始化一个FIFO队列和用于该队列的缓冲区


通过settimer函数 设置一个5秒的超时时间片 数值1对应TIMERCTL结构体里面的data

void CMain(void) {
    ....
    int data = 0;
    int count = 0;
    struct TIMERCTL *timerctl = getTimerController();

    for(;;) {
       char* pStr = intToHexStr(timerctl->timeout);
       boxfill8(shtMsgBox->buf, 160, COL8_C6C6C6, 40, 28, 119, 43);
       showString(shtctl, shtMsgBox, 40, 28, COL8_000000,pStr);

       io_cli();
       if (fifo8_status(&keyinfo) + fifo8_status(&mouseinfo) +
 fifo8_status(&timerinfo)  == 0) {

           io_sti();
       } ....

      else {
      //超时发生后进入这里
           io_sti();
           showString(shtctl, sht_back, 0, 0, COL8_FFFFFF, "5[sec]");
      }
}

在进入内核主循环前 先获取时钟控制器


然后把控制器对应的时间片信息转换成字符串后 显示到Message Box 窗体里


每次循环时 看看控制器对应的数据队列里面是否有数据


如果有数据表明超时发送 于是进入最后的else 部分 在else 里面


我们在桌面的左上角打印出一个字符串”5[sec]”。


3.运行

相关文章
|
12小时前
|
缓存 Java iOS开发
MacOS环境-手写操作系统-13-鼠标中断
MacOS环境-手写操作系统-13-鼠标中断
8 1
|
13小时前
|
存储 Java C语言
MacOS环境-手写操作系统-04-实模式进入保护模式
MacOS环境-手写操作系统-04-实模式进入保护模式
6 1
|
5月前
|
运维 开发工具 C语言
手写操作系统(1)——HelloOS
手写操作系统(1)——HelloOS
140 3
|
10小时前
|
小程序 iOS开发 MacOS
MacOS环境-手写操作系统-44-运行简单的程序
MacOS环境-手写操作系统-44-运行简单的程序
7 0
|
11小时前
|
存储 算法 调度
MacOS环境-手写操作系统-34-进程优先级
MacOS环境-手写操作系统-34-进程优先级
6 0
|
12小时前
|
存储 算法 C语言
MacOS环境-手写操作系统-15-内核管理 检测可用内存
MacOS环境-手写操作系统-15-内核管理 检测可用内存
8 0
|
12小时前
|
缓存 Java C语言
MacOS环境-手写操作系统-12-键盘中断机制
MacOS环境-手写操作系统-12-键盘中断机制为键盘建立中断机制
7 0
|
11小时前
|
存储 算法 调度
MacOS环境-手写操作系统-29-进程切换
MacOS环境-手写操作系统-29-进程切换
5 0
|
11小时前
|
存储 iOS开发 MacOS
MacOS环境-手写操作系统-33-多任务多窗口
MacOS环境-手写操作系统-33-多任务多窗口
7 0
|
12小时前
|
Java C语言 iOS开发
MacOS环境-手写操作系统-11-建立中断机制
本文详细介绍了如何为内核建立中断机制,涉及8259A中断控制器的初始化、中断信号的传递过程以及中断描述符表的设置。通过汇编和C语言代码展示了如何处理中断,特别是键盘和鼠标中断,最后给出了编译和运行的步骤。 摘要由CSDN通过智能技术生成
7 0