zoukankan      html  css  js  c++  java
  • 《30天自制操作系统》笔记(10)——定时器

    《30天自制操作系统》笔记(10)——定时器

    进度回顾

    上一篇上上一篇解决了绘制窗口和窗口刷新的问题。关于窗口的东西就此告一段落。本篇介绍一个相对独立且十分重要的操作系统部件——定时器的使用方法。

    定时器是一个硬件

    可编程的间隔型定时器(Programmable Interval Timer)简称定时器(PIT),是集成到电脑上的一个硬件部件。之前讲过的用于实现中断机制的PIC也是个硬件部件。有了PIT,我们才能在计算机中计时。

    初始化定时器

    前面,CPU、PIC都需要设置好才能用,PIT也需要设置。PIT类似C#Winform里的Timer控件,能设置的只有激发Tick事件的时间间隔(Interval)这个属性。PIT里的Tick事件,对应的是PIC里的0号中断。也就是说,PIT会根据你设定的Interval,每隔Interval时间就发送一个0号中断。这里又印证了"事件小名中断"的说法。

     1 #define PIT_CTRL 0x0043
     2 #define PIT_CNT0 0x0040
     3 void init_pit(void)
     4 {
     5     io_out8(PIT_CTRL, 0x34);/*中断周期(Interval)即将变更*/
     6     io_out8(PIT_CNT0, 0x9c);/*中断周期的低8位*/
     7     io_out8(PIT_CNT0, 0x2e);/*中断周期的高8位*/
     8     return;
     9 }
    10 void HariMain(void)
    11 {
    12     /**/
    13     init_gdtidt();
    14     init_pic();
    15     io_sti(); /* IDT/PIC的初始化已经结束,所以解除CPU的中断禁止 */
    16     fifo8_init(&keyfifo, 32, keybuf);
    17     fifo8_init(&mousefifo, 128, mousebuf);
    18     init_pit();/* 这里! */
    19     io_out8(PIC0_IMR, 0xf8); /* PIT和PIC1和键盘设置为许可(11111000) *//* 这里! */
    20     io_out8(PIC1_IMR, 0xef); /* 鼠标设置为许可(11101111) */
    21     /**/
    22 }

     

    设置中断函数

    设置Tick时,如果指定中断周期为0,会被看做指定为65536。如果设定为1000,中断频率就是1.19318Hz。如果设定为11932,中断频率就是100Hz,即每10ms发生一次中断。11932写成十六进制就是0x2e9c

    PIT会发送0号中断,那就得写一个响应此中断的函数。

    1 void inthandler20(int *esp)
    2 {
    3     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信号接收完了的信息通知PIC */
    4     /* TODO: 暂时什么也不做 */
    5     return;
    6 }
     1 _asm_inthandler20:
     2         PUSH    ES
     3         PUSH    DS
     4         PUSHAD
     5         MOV        EAX,ESP
     6         PUSH    EAX
     7         MOV        AX,SS
     8         MOV        DS,AX
     9         MOV        ES,AX
    10         CALL    _inthandler20 ; 这里会调用void inthandler20(int *esp);函数
    11         POP        EAX
    12         POPAD
    13         POP        DS
    14         POP        ES
    15         IRETD
     1 void init_gdtidt(void)
     2 {
     3     /**/
     4     /* IDT的设定 */
     5     set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);/* 这里! */
     6     set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
     7     set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
     8     set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
     9 
    10     return;
    11 }

    这样就好了。

    用PIT做点什么呢?

    Hello PIT!

    保守起见,先做个PIT的hello world比较好。

     1 struct TIMERCTL {
     2     unsigned int count;
     3 };
     4 struct TIMERCTL timerctl;
     5 void init_pit(void)
     6 {
     7     io_out8(PIT_CTRL, 0x34);
     8     io_out8(PIT_CNT0, 0x9c);
     9     io_out8(PIT_CNT0, 0x2e);
    10     timerctl.count = 0;/* 这里! */
    11     return;
    12 }
    13 void inthandler20(int *esp)
    14 {
    15     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信号接收完了的信息通知PIC */
    16     timerctl.count++;/* 这里! */
    17     return;
    18 }
    19 void HariMain(void)
    20 {
    21     /**/
    22     for (;;) {
    23         sprintf(s, "%010d", timerctl.count);/* 这里! */
    24         boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
    25         putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
    26         sheet_refresh(sht_win, 40, 28, 120, 44);
    27         
    28         /**/
    29     }
    30 }

    效果如下图所示。

    定时器的hello world!

    超时功能

    定时器经常被用于这样一种情形:"hi操作系统老兄!麻烦你10秒钟后通知我一下,我要执行某函数M"。这样的功能就叫做超时(timeout)

     1 struct TIMERCTL {
     2     unsigned int count;
     3     unsigned int timeout;
     4     struct FIFO8 *fifo;
     5     unsigned char data;
     6 };
     7 void init_pit(void)
     8 {
     9     io_out8(PIT_CTRL, 0x34);
    10     io_out8(PIT_CNT0, 0x9c);
    11     io_out8(PIT_CNT0, 0x2e);
    12     timerctl.count = 0;
    13     timerctl.timeout = 0;
    14     return;
    15 }
    16 void inthandler20(int *esp)
    17 {
    18     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信号接收完了的信息通知PIC */
    19     timerctl.count++;
    20     if (timerctl.timeout > 0) { /* 如果已经设定了超时 */
    21         timerctl.timeout--;
    22         if (timerctl.timeout == 0) {
    23             fifo8_put(timerctl.fifo, timerctl.data);
    24         }
    25     }
    26     return;
    27 }
    28 void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
    29 {
    30     int eflags;
    31     eflags = io_load_eflags();
    32     io_cli();
    33     timerctl.timeout = timeout;
    34     timerctl.fifo = fifo;
    35     timerctl.data = data;
    36     io_store_eflags(eflags);
    37     return;
    38 }
    39 void HariMain(void)
    40 {
    41     /**/
    42     struct FIFO8 timerfifo;
    43     char s[40], keybuf[32], mousebuf[128], timerbuf[8];
    44     /**/
    45     fifo8_init(&timerfifo, 8, timerbuf);
    46     settimer(1000, &timerfifo, 1);
    47     /**/
    48     for (;;) {
    49         /**/
    50         io_cli();
    51         if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) {
    52             io_sti();
    53         } else {
    54             if (fifo8_status(&keyfifo) != 0) {
    55                 /**/
    56             } else if (fifo8_status(&mousefifo) != 0) {
    57                 /**/
    58             } else if (fifo8_status(&timerfifo) != 0) {
    59                 i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */
    60                 io_sti();
    61                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
    62                 sheet_refresh(sht_back, 0, 64, 56, 80);
    63             }
    64         }
    65     }
    66 }
    超时功能的hello world

    程序很简单,我们在其中设定10秒钟后向timerinfo写入"1"(暂时没什么特别的含义,写"2"也没问题),而timerinfo接收到数据时,就会在屏幕上显示"10[sec]"。

    图就不贴了,没什么新东西。

    设定多个定时器

    很多应用程序都会使用定时器,所以PIT要能够变幻出多个定时器。

     1 #define MAX_TIMER        500
     2 struct TIMER {
     3     unsigned int timeout, flags;
     4     struct FIFO8 *fifo;
     5     unsigned char data;
     6 };
     7 struct TIMERCTL {
     8     unsigned int count;
     9     struct TIMER timer[MAX_TIMER];
    10 };
    11 
    12 #define TIMER_FLAGS_ALLOC        1    /* 已配置状态 */
    13 #define TIMER_FLAGS_USING        2    /* 定时器运行中 */
    14 void init_pit(void)
    15 {
    16     int i;
    17     io_out8(PIT_CTRL, 0x34);
    18     io_out8(PIT_CNT0, 0x9c);
    19     io_out8(PIT_CNT0, 0x2e);
    20     timerctl.count = 0;
    21     for (i = 0; i < MAX_TIMER; i++) {
    22         timerctl.timer[i].flags = 0; /* 未使用 */
    23     }
    24     return;
    25 }
    26 struct TIMER *timer_alloc(void)
    27 {
    28     int i;
    29     for (i = 0; i < MAX_TIMER; i++) {
    30         if (timerctl.timer[i].flags == 0) {
    31             timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
    32             return &timerctl.timer[i];
    33         }
    34     }
    35     return 0; /* 没找到 */
    36 }
    37 void timer_free(struct TIMER *timer)
    38 {
    39     timer->flags = 0; /* 未使用 */
    40     return;
    41 }
    42 void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
    43 {
    44     timer->fifo = fifo;
    45     timer->data = data;
    46     return;
    47 }
    48 void timer_settime(struct TIMER *timer, unsigned int timeout)
    49 {
    50     timer->timeout = timeout;
    51     timer->flags = TIMER_FLAGS_USING;
    52     return;
    53 }
    54 void inthandler20(int *esp)
    55 {
    56     int i;
    57     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信号接收完了的信息通知PIC */
    58     timerctl.count++;
    59     for (i = 0; i < MAX_TIMER; i++) {
    60         if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
    61             timerctl.timer[i].timeout--;
    62             if (timerctl.timer[i].timeout == 0) {
    63                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
    64                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
    65             }
    66         }
    67     }
    68     return;
    69 }
    设定MAX_TIMER个定时器
     1 void HariMain(void)
     2 {
     3     /**/
     4     struct FIFO8 timerfifo, timerfifo2, timerfifo3;
     5     char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8];
     6     struct TIMER *timer, *timer2, *timer3;
     7     /**/
     8     fifo8_init(&timerfifo, 8, timerbuf);
     9     timer = timer_alloc();
    10     timer_init(timer, &timerfifo, 1);
    11     timer_settime(timer, 1000);
    12     fifo8_init(&timerfifo2, 8, timerbuf2);
    13     timer2 = timer_alloc();
    14     timer_init(timer2, &timerfifo2, 1);
    15     timer_settime(timer2, 300);
    16     fifo8_init(&timerfifo3, 8, timerbuf3);
    17     timer3 = timer_alloc();
    18     timer_init(timer3, &timerfifo3, 1);
    19     timer_settime(timer3, 50);
    20     /**/
    21     for (;;) {
    22         /**/
    23         io_cli();
    24         if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo)
    25                 + fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) {
    26             io_sti();
    27         } else {
    28             if (fifo8_status(&keyfifo) != 0) {
    29                 /**/
    30             } else if (fifo8_status(&mousefifo) != 0) {
    31                 /**/
    32             } else if (fifo8_status(&timerfifo) != 0) {
    33                 i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */
    34                 io_sti();
    35                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
    36                 sheet_refresh(sht_back, 0, 64, 56, 80);
    37             } else if (fifo8_status(&timerfifo2) != 0) {
    38                 i = fifo8_get(&timerfifo2); /* 首先读入(为了设定起始点) */
    39                 io_sti();
    40                 putfonts8_asc(buf_back, binfo->scrnx, 0, 80, COL8_FFFFFF, "3[sec]");
    41                 sheet_refresh(sht_back, 0, 80, 48, 96);
    42             } else if (fifo8_status(&timerfifo3) != 0) {/* 模拟光标闪烁 */
    43                 i = fifo8_get(&timerfifo3);
    44                 io_sti();
    45                 if (i != 0) {
    46                     timer_init(timer3, &timerfifo3, 0); /* 然后设置0 */
    47                     boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
    48                 } else {
    49                     timer_init(timer3, &timerfifo3, 1); /* 然后设置1 */
    50                     boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
    51                 }
    52                 timer_settime(timer3, 50);
    53                 sheet_refresh(sht_back, 8, 96, 16, 112);
    54             }
    55         }
    56     }
    57 }
    使用多个定时器

     

    定时器优化

    前面都算是使用定时器的实验,以此为基础进行优化,使其更实用。

    原作者的优化进行了好几步,在此仅罗列一下,并给出最后的程序。

    • 将timeout的含义从"所剩时间"改变为"予定时间",这样就可以去掉inthandler20(int*)函数里的"timerctl.timer[i].timeout--"。
    • 现在的定时器,每隔42949673秒(497天)后count就是0xFFFFFFFF了,在这之前必须重启计算机,否则程序就会出错。因此让OS每隔一年自动调整一次。
    • timer数组按timeout升序排序,在inthandler20(int*)中每次只检查第一个timer元素即可。
    • 上一步中,发现超时时,inthandler20(int*)会准备下一个要检查的timer,这延长了处理时间。为解决这个问题,增加变量using,用于记录有几个定时器处于活动中(需要检查)。(类似于窗口图层部分的sheet中的top)不过这样只能缓解问题,不能彻底解决问题。
    • 将静态数组timers改为链表,从而省掉了上一步中可能发生的移位操作。
    • 使用数据结构中的"哨兵"概念简化上一步的链表处理函数。"哨兵"是为了简化循环的边界条件而引入的。在timers链表最后加上一个timeout为0xFFFFFFFF的定时器(作为哨兵)。由于OS会在1年后将定时器count重置,所以这个哨兵定时器永远不会到达触发的时候。这其实就是永恒吊车尾啊。不管你信不信,添上这样一个吊车尾就可以减少链表相关的代码。

    经过若干次优化后的代码如下。

     1 void init_pit(void)
     2 {
     3     int i;
     4     struct TIMER *t;
     5     io_out8(PIT_CTRL, 0x34);
     6     io_out8(PIT_CNT0, 0x9c);
     7     io_out8(PIT_CNT0, 0x2e);
     8     timerctl.count = 0;
     9     for (i = 0; i < MAX_TIMER; i++) {
    10         timerctl.timers0[i].flags = 0; /* 没有使用 */
    11     }
    12     t = timer_alloc(); /* 取得一个 */
    13     t->timeout = 0xffffffff;
    14     t->flags = TIMER_FLAGS_USING;
    15     t->next = 0; /* 末尾 */
    16     timerctl.t0 = t; /* 因为现在只有哨兵,所以他就在最前面 */
    17     timerctl.next = 0xffffffff; /* 因为只有哨兵,所以下一个超时时刻就是哨兵的时刻 */
    18     return;
    19 }
     1 void timer_settime(struct TIMER *timer, unsigned int timeout)
     2 {
     3     int e;
     4     struct TIMER *t, *s;
     5     timer->timeout = timeout + timerctl.count;
     6     timer->flags = TIMER_FLAGS_USING;
     7     e = io_load_eflags();
     8     io_cli();
     9     t = timerctl.t0;
    10     if (timer->timeout <= t->timeout) {
    11         /* 插入最前面的情况 */
    12         timerctl.t0 = timer;
    13         timer->next = t; /* 下面是设定t */
    14         timerctl.next = timer->timeout;
    15         io_store_eflags(e);
    16         return;
    17     }
    18     /* 搜寻插入位置 */
    19     for (;;) {
    20         s = t;
    21         t = t->next;
    22         if (timer->timeout <= t->timeout) {
    23             /* 插入s和t之间的情况 */
    24             s->next = timer; /* s下一个是timer */
    25             timer->next = t; /* timer的下一个是t */
    26             io_store_eflags(e);
    27             return;
    28         }
    29     }
    30 }
     1 void inthandler20(int *esp)
     2 {
     3     struct TIMER *timer;
     4     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00接收信号结束的信息通知给PIC */
     5     timerctl.count++;
     6     if (timerctl.next > timerctl.count) {
     7         return;
     8     }
     9     timer = timerctl.t0; /* 首先把最前面的地址赋给timer */
    10     for (;;) {
    11         /* 因为timers的定时器都处于运行状态,所以不确认flags */
    12         if (timer->timeout > timerctl.count) {
    13             break;
    14         }
    15         /* 超时 */
    16         timer->flags = TIMER_FLAGS_ALLOC;
    17         fifo32_put(timer->fifo, timer->data);
    18         timer = timer->next; /* 将下一个定时器的地址代入timer */
    19     }
    20     /* 新移位 */
    21     timerctl.t0 = timer;
    22     /* timerctl.next的设定 *//* 这里 */
    23     timerctl.next = timer->timeout;
    24     return;
    25 }

    曾经引入的using变量现在又被去掉了。

    总结

    定时器是如此重要,以至于我一时想不出它有多重要。定时器使用起来并不复杂,只不过为了尽可能优化提高效率,原作者讲了很多链表之类的数据结构和算法的东西。

    到现在,终于看完了《30天自制操作系统》的三分之一。收获么,可以说是坚定了我之前对软件工程的理念,也可以说是加强了自我封闭和顽固的理由。数字电路构成了硬件,但从软件工程师的角度看,硬件也是一种软件,它为上层软件(操作系统)提供了API。操作系统则为应用程序提供了API。如果应用程序做成插件式的,那这个应用程序也可以被称为一个"操作系统",或者叫做"平台"(例如chrome OS、Visual Studio、Eclipse)。这就像计算机网络体系结构一样,分为多个层,每个下层都为上层提供API,上层不必知道下层的实现原理,直接使用就行了。

    很快就要进入"多任务"的设计实现了!

    请查看下一篇《《30天自制操作系统》笔记(11)——高分辨率》

  • 相关阅读:
    【DL-2-2】卷积神经网络(CNN)--AlexNet、ZFNet、VGGNet、GoogleNet、ResNet
    Python3 错误和异常-(try/except/else/finally/raise/assert)
    生成器 Generators
    Map,Filter 和 Reduce
    装饰器
    目标检测:介绍及传统方法
    【ML-17-2】MCMC--马尔可夫蒙特卡罗方法(MH和Gibbs)
    如何在JDK1.8中愉快地处理日期和时间
    luogu1447 能量采集
    luogu1775 古代人的难题 打表找规律
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/OS-in-30-days-10-programmable-interval-timer.html
Copyright © 2011-2022 走看看