IT评测·应用市场-qidao123.com技术社区

标题: 30天开发操作体系 第 12 天 -- 定时器 [打印本页]

作者: 吴旭华    时间: 2025-1-8 16:22
标题: 30天开发操作体系 第 12 天 -- 定时器
媒介

定时器(Timer)对于操作体系非常紧张。它在原理上却很简单,只是每隔一段时间(比如0.01秒)就发送一个中断信号给CPU。幸亏有了定时器,CPU才不用辛劳地去计量时间。……如果没有定时器会怎么样呢?让我们想象一下吧。
如果CPU看不到定时器而仍想计量时间的话,就只能牢记每一条指令的实验时间了。比如,往寄存器写人常数的MOV指令是1个时钟周期(Clock):加法计算的ADD指令原则上是1个时钟周期,但根据条件差别大概是2个时钟周期……等等。CPU不仅要牢记这些内容,然后还要据此调查一下调用这些函数所需的时间,比如,调用这个函数必要150个时钟周期,调用谁人函数因参数差别必要106到587个时钟周期等。
而这里的“时钟周期”又不是一个固定值。比如CPU主频是100MHz的话,一个时钟周期是10 纳秒;但主频如果是200MHz,1个时钟周期就是5纳秒。既然CPU有各种主频,那么1个时钟周期的时间也就各不雷同。(大家这下明白cpu的巨细核了吧)
这样做可以委曲通过程序对时间举行管理,实现每隔一定时间举行一次某种处理,比如让钟表(程序)的秒针动起来。如果程序中时间计算出错了,那么做出的钟表不是快就是慢,没法使用。
如果没有定时器,还会出现别的麻烦,即不能使用HLT指令。完成这个指令所需的时钟周期不是个固定值。这样,一旦实验HLT指令,程序就不知道时间了。不能实验HLT指令,就意味着要浪费很多电能。以是只能二选一,要么放弃时间的计量,要么选择浪费电能。左右为难,着实糟糕透顶。
打个比方说,如果大家没有手表还想知道时间,那该怎么办呢?当然,不准看太阳,也不准看星星。那就只能根据肚子的饥饿水平,大概烧一壶开水所用的时间等方法来判断了。
总之只能是一边干点儿什么,一边计算时间,而且决不能睡觉!一睡觉就没法计时了……就类似这种情况。
然而实际上,由于有定时器中断,以是不用担心会发生这样的悲剧。程序只必要以自己的步
调处理自己的问题就行了。至于到底经过了多长时间,只要在中断处理程序中数一数定时器中断发生的次数就可以了。就算CPU处于HLT状态,也可以通过中断来唤醒。根本就没须要让程序自己去记忆时间。CPU也就可以安心地去睡觉了(HT)。这样,大家还可以省点电费(笑)。
以是说定时器非常紧张。管理定时器是操作体系的庞大任务之一,以是在我们的操作体系中也要使用定时器。
一、定时器的设定和使用

1.定时器使用前的设定

要在电脑中管理定时器,只需对PIT举行设定就可以了。PIT是“ProgrammableInterval Timer的缩写,翻译过来就是“可编程的隔断型定时器”。我们可以通过设定PIT,让定时器每隔多少秒就产生一次中断。由于在电脑中PIT连接着IRQ(imteruptrequest,参考第6天)的0号,以是只要设定了PIT就可以设定IRQ0的中断隔断。……在旧机种上PIT是作为一个独立的芯片安装在主板上的,而现在已经和PIC(programmableinterruptcontroller,参考第6天)一样被集成到别的芯片里了。
前几天我们学习PIC时曾经非常辛劳,从现在开始,我们又要重温那种感觉了。大家可不要想:“怎么又学这个?”刚开始学习PIC时,陌生的东西比较多,学起来很费力。这次就不会那么辛劳了。
电脑里的定时器用的是8254芯片(或其替代品 ),那就查一下这个芯片吧。
IRQ0的中断周期变更:
■ AL=0x34:OUT(0x43,AL);
■ AL=中断周期的低8位;OUT(0x40,AL);
■ AL=中断周期的高8位;OUT(0x40,AL);
■ 到这里告一段落。
■ 如果指定中断周期为0,会被看作是指定为65536。实际的中断产生的频率是单位时间时钟周期数(即主频)/设定的数值。比如设定值如果是1000,那么中断产生的频率就是1.19318KHz。设定值是10000的话,中断产生频率就是119.318Hz。再比如设定值是11932的话,中断产生的频率约莫就是100Hz了,即每10ms发生一次中断。
我们不清楚其中的详细原理,只知道只要实验3次OUT指令设定就完成了。将中断周期设定为11932的话,中断频率好像就是100Hz,也就是说1秒钟会发生100次中断。那么我们就设定成这个值吧。把11932换算成十六进制数就是0x2e9c,下面是我们编写的函数initpit。
  1. #define PIT_CTRL        0x0043
  2. #define PIT_CNT0        0x0040
  3. void init_pit(void)
  4. {
  5.         io_out8(PIT_CTRL, 0x34);
  6.         io_out8(PIT_CNT0, 0x9c);
  7.         io_out8(PIT_CNT0, 0x2e);
  8.         return;
  9. }
复制代码
  1. void HariMain(void)
  2. {
  3.         ...
  4.         init_gdtidt();
  5.         init_pic();
  6.         io_sti(); /* IDT/PIC的初始化已经结束,所以解除CPU的中断禁止 */
  7.         fifo8_init(&keyfifo, 32, keybuf);
  8.         fifo8_init(&mousefifo, 128, mousebuf);
  9.         init_pit(); /* 这里 */
  10.         ...
  11. }
复制代码
这样的话IRQ0就会在1秒钟内发生100次中断了。
下面我们来编写IRO0发生时所调用的中断处理程序。它险些和键盘中断处理程序一样,大家还记得吗:
  1. void inthandler20(int *esp)
  2. {
  3.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收完了的信息通知给PIC */
  4.         /* 暂时什么也不做 */
  5.         return;
  6. }
复制代码
我们把 init_pit 和 inthandler20 放到了新创建的文件夹 timer.c
下面是naskfunc.nas新增的:
  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
  11.                 POP                EAX
  12.                 POPAD
  13.                 POP                DS
  14.                 POP                ES
  15.                 IRETD
复制代码
大家还记得下一步是要干什么吗?
为了把这个中断处理程序注册到IDT,inlt gdtidt函数中也要加上几行。这也和键盘处理的时候差不多哦:
  1. /* IDT的设定 */
  2.         set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
  3.         set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
  4.         set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
  5.         set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
复制代码
到这里预备工作就完成了。也不知能不能正常运行。正常的话,嗯,应该什么都不发生。
下面我们实验“make run”。哦,什么也没发生。太好了!但这样有点不过瘾,还是在中断处理程序中做点什么吧!
2.计量时间

那我们让它干点什么呢?……我们就让它实验下面这段程序吧:
bookpack.h
  1. struct TIMERCTL {
  2.         unsigned int count;
  3. };
复制代码
  1. #include "bootpack.h"
  2. #define PIT_CTRL        0x0043
  3. #define PIT_CNT0        0x0040
  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. }
复制代码
大家瞅瞅,没几行新增代码。程序所做的处理是:起首定义了struct TIMERCTL布局体。然后,在布局体内定义了一个计数变量 count。初始化PIT时,将这个计数变量设置为0。每次发生定时器中断时,计数变量就以1递增。
也就是说,即使这个计数变量在HariMain中不举行加算,每1秒钟它也会主动增长100。
为了确认,我们把数值显示出来吧。
  1.                 ...
  2.                 for (;;) {
  3.                         sprintf(s, "%010d", timerctl.count);
  4.                         boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
  5.                         putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
  6.                         sheet_refresh(sht_win, 40, 28, 120, 44);
  7.                 ...
复制代码
这样的话,数字应该是以每秒钟100的速度增长。而且岂论哪个机种增长速度都是一样的。
即使CPU的速度差别,增长速度也应该是一样的。我们先做做看吧。实验“make run"。……正常运行了,还算顺利。

就能知道从启动开始时间过去了多少秒。如果往方便面里倒入开水的同时使用这个方法,就能测量是否到3分钟(=180秒)了。哦,终于向着有实用价值的操作体系迈出了第一步。
现在,从启动开始经过了多少秒这一类问题,我们就可以很轻松地判断了。别的,我们还可以计量处理所淹灭的时间。详细做法是,处理前看一下时间并把它存放到一个变量里,处理竣事之后再看一下时间,然后只要用减法算出时间差,就能得到答案了,比如“这个处理耗时13.56秒”等。我们乃至可以据此编制基准测试程序(benchmark program)。
这里大家稍稍追念一下,现在已经能够显示出窗口,又能使用鼠标,又能计量时间,还能举行内存管理,已经实现了很多功能。有了这些功能,只要对它们举行各种组合,就能做很多事情。
我们言归正传,继续说定时器吧。操作体系的定时器常常被用于这样一种情况:“喂,操作体系,过了10秒钟以后通知我一声,我要干什么什么”。当然,不一定非要是10秒,也可以是1秒或30分钟。我们把这样的功能叫做“超时”(timeout)。下面就来实现这个功能吧。
起首往布局体struct TIMERCTL里添加一些代码,以便记录有关超时的信息。
  1. struct TIMERCTL {
  2.         unsigned int count;
  3.         unsigned int timeout;
  4.         struct FIFO8 *fifo;
  5.         unsigned char data;
  6. };
复制代码
以上布局体中的timeout用来记录离超时另有多长时间。一旦这个剩余时间达到0,程序就往FIFO缓冲区里发送数据。定时器就是通过这种方法通知HariMain时间到了。至于为什么要使用FIFO缓冲区,也说不上个以是然,只是觉得这个方法简单,由于使用FIFO缓冲区来通知的话,可以比照键盘和鼠标,使用同样的方法来处理。
下面我们来修改函数吧。
  1. #include "bootpack.h"
  2. #define PIT_CTRL        0x0043
  3. #define PIT_CNT0        0x0040
  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.         timerctl.timeout = 0;
  12.         return;
  13. }
  14. void inthandler20(int *esp)
  15. {
  16.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收结束的信息通知给PIC */
  17.         timerctl.count++;
  18.         if (timerctl.timeout > 0) { /* 如果已经设定了超时 */
  19.                 timerctl.timeout--;
  20.                 if (timerctl.timeout == 0) {
  21.                         fifo8_put(timerctl.fifo, timerctl.data);
  22.                 }
  23.         }
  24.         return;
  25. }
  26. void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
  27. {
  28.         int eflags;
  29.         eflags = io_load_eflags();
  30.         io_cli();
  31.         timerctl.timeout = timeout;
  32.         timerctl.fifo = fifo;
  33.         timerctl.data = data;
  34.         io_store_eflags(eflags);
  35.         return;
  36. }
复制代码
盼望大家注意的是,我们在inthandler20函数里实现了超时功能。每次发生中断时就把timeout减1,减到0时,就向fifo发送数据。
在settimer函数里,如果设定还没有完全竣事IRQ0的中断就进来的话,会引起紊乱,以是我们先禁止中断,然后完成设定,最后再把中断状态复原。
这在HariMain中如何实现呢?我们来实验这样做:
  1. void HariMain(void)
  2. {
  3.         struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
  4.         struct FIFO8 timerfifo;
  5.         char s[40], keybuf[32], mousebuf[128], timerbuf[8];
  6.         ...
  7.         fifo8_init(&timerfifo, 8, timerbuf);
  8.         settimer(1000, &timerfifo, 1);
  9.         ...
  10.         for (;;) {
  11.                 ...
  12.                 io_cli();
  13.                 if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) {
  14.                         io_sti();
  15.                 } else {
  16.                         if (fifo8_status(&keyfifo) != 0) {
  17.                                 ...
  18.                         } else if (fifo8_status(&mousefifo) != 0) {
  19.                                 i = fifo8_get(&mousefifo);
  20.                                 io_sti();
  21.                                 if (mouse_decode(&mdec, i) != 0) {
  22.                                         ...
  23.                         } else if (fifo8_status(&timerfifo) != 0) {
  24.                                 i = fifo8_get(&timerfifo); /* 读入 */
  25.                                 io_sti();
  26.                                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
  27.                                 sheet_refresh(sht_back, 0, 64, 56, 80);
  28.                         }
  29.                 }
  30.         }
  31. }
复制代码
程序很简单,我们在其中设定10秒钟以后向timerfifo写人“1”这个数据,而timerffo接收到数据时,就会在屏幕上显示“10[sec]”。
我们实验一下“make run”,看,显示出来了!

2.设定多个计时器

在上一节做的超时功能,超时竣事后如果再设定1000的话,那我们就可以让它每10秒显示或是让它一闪一灭地显示。别的,隔断不仅限于10秒,我们还可以设定得更长一些或更短一次,些。比如设定为0.5秒的隔断可以用于文字输人时的光标闪烁。
开发操作体系时,超时功能非常方便,以是在很多地方都可以使用它。比如可以让电子时钟每隔1秒重新显示一次;演奏音乐时,可以用它计量音符的是非;也可以让它以0.1秒1次的频率来监督没有中断功能的装置;别的,还可以用它实现光标的闪烁功能。
为了简单地实现这些功能,我们要预备很多能够设定超时的定时器。
起首把struct TIMERCTL修改成下面这样。
  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. };
复制代码
这样超时定时器最多就可以设定为500个了,fags则用于记录各个定时器的状态。
继续修改对应的函数:
  1. #include "bootpack.h"
  2. #define PIT_CTRL        0x0043
  3. #define PIT_CNT0        0x0040
  4. struct TIMERCTL timerctl;
  5. #define TIMER_FLAGS_ALLOC                1        /* 已配置状态 */
  6. #define TIMER_FLAGS_USING                2        /* 定时器运行中 */
  7. void init_pit(void)
  8. {
  9.         int i;
  10.         io_out8(PIT_CTRL, 0x34);
  11.         io_out8(PIT_CNT0, 0x9c);
  12.         io_out8(PIT_CNT0, 0x2e);
  13.         timerctl.count = 0;
  14.         for (i = 0; i < MAX_TIMER; i++) {
  15.                 timerctl.timer[i].flags = 0; /* 未使用 */
  16.         }
  17.         return;
  18. }
  19. struct TIMER *timer_alloc(void)
  20. {
  21.         int i;
  22.         for (i = 0; i < MAX_TIMER; i++) {
  23.                 if (timerctl.timer[i].flags == 0) {
  24.                         timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
  25.                         return &timerctl.timer[i];
  26.                 }
  27.         }
  28.         return 0; /* 没找到 */
  29. }
  30. void timer_free(struct TIMER *timer)
  31. {
  32.         timer->flags = 0; /* 未使用 */
  33.         return;
  34. }
  35. void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
  36. {
  37.         timer->fifo = fifo;
  38.         timer->data = data;
  39.         return;
  40. }
  41. void timer_settime(struct TIMER *timer, unsigned int timeout)
  42. {
  43.         timer->timeout = timeout;
  44.         timer->flags = TIMER_FLAGS_USING;
  45.         return;
  46. }
  47. void inthandler20(int *esp)
  48. {
  49.         int i;
  50.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收结束的信息通知给PIC */
  51.         timerctl.count++;
  52.         for (i = 0; i < MAX_TIMER; i++) {
  53.                 if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
  54.                         timerctl.timer[i].timeout--;
  55.                         if (timerctl.timer[i].timeout == 0) {
  56.                                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
  57.                                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
  58.                         }
  59.                 }
  60.         }
  61.         return;
  62. }
复制代码
程序轻微有些长,不是很难,只要前面的程序大家都明白了,这里应该也没什么困难。
最后来看HariMain函数。我们不一定都设定为10秒,也实验一下设为3秒吧。别的,我们还要编写类似光标闪烁那样的程序。
  1. void HariMain(void)
  2. {
  3.         struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
  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.                                 i = fifo8_get(&mousefifo);
  32.                                 io_sti();
  33.                                 if (mouse_decode(&mdec, i) != 0) {
  34.                                        
  35.                         } else if (fifo8_status(&timerfifo) != 0) {
  36.                                 i = fifo8_get(&timerfifo); /* 读入 */
  37.                                 io_sti();
  38.                                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
  39.                                 sheet_refresh(sht_back, 0, 64, 56, 80);
  40.                         } else if (fifo8_status(&timerfifo2) != 0) {
  41.                                 i = fifo8_get(&timerfifo2); /* 读入 */
  42.                                 io_sti();
  43.                                 putfonts8_asc(buf_back, binfo->scrnx, 0, 80, COL8_FFFFFF, "3[sec]");
  44.                                 sheet_refresh(sht_back, 0, 80, 48, 96);
  45.                         } else if (fifo8_status(&timerfifo3) != 0) {/* 模拟光标 */
  46.                                 i = fifo8_get(&timerfifo3);
  47.                                 io_sti();
  48.                                 if (i != 0) {
  49.                                         timer_init(timer3, &timerfifo3, 0); /* 设置 0 */
  50.                                         boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
  51.                                 } else {
  52.                                         timer_init(timer3, &timerfifo3, 1); /* 设置 1 */
  53.                                         boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
  54.                                 }
  55.                                 timer_settime(timer3, 50);
  56.                                 sheet_refresh(sht_back, 8, 96, 16, 112);
  57.                         }
  58.                 }
  59.         }
  60. }
复制代码
下面就是期盼已久的“make run”了。我们实验一下,看看我们的成绩:

二、加快中断处理

1.0

现在我们可以自由使用多个定时器了,从数量上说,已经富足了。但仔细看一下大家会发现,inthandler20另有很大问题:中断处理本来应该在很短的时间内完成,可使用inthandler20时却淹灭了很长时间。这就妨碍了其他中断处理的实验,使得操作体系反应很迟钝。
如果检査inthandler20,能发现每次举行定时器中断处理的时候,都会对所有运动中的定时器
举行“timerctl.timer.timeout–;”处理。也就是说,CPU要完成从内存中读取变量值,减去1,然后又往内存中写人的操作。本来谁也不会注意到这种渺小之处,但由于我们想在中断处理程序中尽大概淘汰哪怕是一点点工作量,以是才会注意到这里。
问题找到了,那该怎么修改才好呢?我们看看下面这样行不行:
  1. void inthandler20(int *esp)
  2. {
  3.         int i;
  4.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收结束的信息通知给PIC */
  5.         timerctl.count++;
  6.         for (i = 0; i < MAX_TIMER; i++) {
  7.                 if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
  8.                         if (timerctl.timer[i].timeout <= timerctl.count) {
  9.                                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
  10.                                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
  11.                         }
  12.                 }
  13.         }
  14.         return;
  15. }
复制代码
我们改变了程序中变量timer.timeout的含义。它指的不再是“所剩时间”,而是“设定时刻”
了。由于现在的时刻计数到timerctl.count中去了,以是就拿它和timer.timeout举行比较,如果雷同或是超过了,就通过往FIFO缓冲区里传送数据来通知HariMain。大家现在再看一看,我们一直担心的减法计算没有了。这样一改,程序的速度应该能轻微变快一些了。
下面我们也要相应地修改timer_settime函数。
  1. void timer_settime(struct TIMER *timer, unsigned int timeout)
  2. {
  3.         timer->timeout = timeout + timerctl.count;
  4.         timer->flags = TIMER_FLAGS_USING;
  5.         return;
  6. }
复制代码
timer_settime函数中所指定的时间,是“从现在开始多少多少秒以后”的意思,以是用这个
时间加上现在的时刻,就可以计算出中断的预定时刻。程序中对这个时刻举行了记录。别的地方就不用改了。
到底这样做行不行呢,我们实验一下“make run”。好哇,举行得很顺利。固然还没能切身
感到速度变快了多少,不过先自我满意一下吧。
同时也正是由于变成了这种方式,在我们这个操作体系中,启动以后经过42949673
秒后,count就是0xfffffffff了,比这个值再大就不能设定了。这么多秒是几天呢?……嗯,请稍等(用计算器算一下)……约莫是497天。也就是约莫一年就要重新启动一次操作体系,让count归0。
这里大家大概又会有怨言了“哎呀,还必要重新起动,这样的操作体系真是麻烦”。事实上
本人也是这么想的(笑)。怎么办才好呢。回到上一节的做法,好欠好呢?可是回到上
一节的做法,速度又有些慢。……既不盼望速度慢,又不想重新启动 – 为了满意这种奢望,我们设计成一年调整一次时刻的程序也许比较好。
  1. int t0=timerctl.count;/*所有时刻都要减去这个值*/
  2. io_cli();/*在时刻调整时禁止定时器中断 */
  3. timerctl.count-=t0;
  4. for(i=0;i<MAX TIMER;i++){
  5.         if(timerctl.timeri],flags ==TIMER FLAGS USING){
  6.                 timerctl.timer[i].timeout =t;
  7.         }
  8. }
  9. io_sti();
复制代码
也许以上方法并非最好,但我们不轻言放弃而去想办法解决,这种心境是最紧张的。只要努
力,我们肯定还能找到别的好办法。
2.0

我们再来改善一下吧。
代码如下(示例):
  1. void inthandler20(int *esp)
  2. {
  3.         int i;
  4.         io_out8(PIC0_OCW2, 0x60);
  5.         timerctl.count++;
  6.         for (i = 0; i < MAX_TIMER; i++) {
  7.                 if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
  8.                         if (timerctl.timer[i].timeout <= timerctl.count) {
  9.                                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
  10.                                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
  11.                         }
  12.                 }
  13.         }
  14.         return;
  15. }
复制代码
如果看一下 harib09e的inthandler20,大家会发现每次中断都要实验500次(=MAX TIMER的
次数)if语句,很浪费时间。由于1秒钟就要发生100次中断,这个if语句1秒钟就要实验5万次。
只管云云,这两个if语句都为真,而其中的fags值得以更改,大概是6fo8put函数能够实验的频
率,最多也就是每0.5秒1次,即每秒2次左右。其余的49998次if语句都是在做无勤奋,基本没什么意义。
我们来变通一下思索方式,如果是人在举行着这样的定时器管理,会怎么做呢?定时器加在
一起最多有500个。其中有3秒钟以后超时的,有50秒钟以后超时的,也有0.3秒钟以后超时的,另有一天以后超时的。这种情况下,我们起首会关注哪一个?应该是0.3秒钟以后的谁人吧。0.3秒钟的竣事后,下次是3秒钟以后的。也就是没须要把500个都看完,只要看到“下一个”的时刻就可以了。因此,我们追加一个变量timerctl.next,让它记住下一个时刻。
  1. struct TIMERCTL {
  2.         unsigned int count, next;
  3.         struct TIMER timer[MAX_TIMER];
  4. };
复制代码
  1. void inthandler20(int *esp)
  2. {
  3.         int i;
  4.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收结束的信息通知给PIC */
  5.         timerctl.count++;
  6.         if (timerctl.next > timerctl.count) {
  7.                 return; /* 还不到下一个时刻,所以结束 */
  8.         }
  9.         timerctl.next = 0xffffffff;
  10.         for (i = 0; i < MAX_TIMER; i++) {
  11.                 if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
  12.                         if (timerctl.timer[i].timeout <= timerctl.count) {
  13.                                 /* 超时 */
  14.                                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
  15.                                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
  16.                         } else {
  17.                                 /* 还没有超时的话,这里会找到离下一次超时最近的*/
  18.                                 if (timerctl.next > timerctl.timer[i].timeout) {
  19.                                         timerctl.next = timerctl.timer[i].timeout;
  20.                                 }
  21.                         }
  22.                 }
  23.         }
  24.         return;
  25. }
复制代码
固然程序变长了,但要做的处理却淘汰了。在大多数情况下,第一个if语句的return都会实验,
中断处理就到此竣事了。当到达下一个时刻时,使用之前那种方法检查是否超时。超时的话,就写人到FIFO中;还没超时的话就调查是否将其设定为下一个时刻(未超时时刻中,最小的时刻是下一个时刻)。
如果用这样的方法,就能大大淘汰没有意义的if语句的实验次数,速度也应该快多了。
由于使用了next,以是其他地方也要修改一下。
  1. void init_pit(void)
  2. {
  3.         int i;
  4.         io_out8(PIT_CTRL, 0x34);
  5.         io_out8(PIT_CNT0, 0x9c);
  6.         io_out8(PIT_CNT0, 0x2e);
  7.         timerctl.count = 0;
  8.         timerctl.next = 0xffffffff; /* 因为最初没有正在运行的定时器 */
  9.         for (i = 0; i < MAX_TIMER; i++) {
  10.                 timerctl.timer[i].flags = 0; /* 没有使用 */
  11.         }
  12.         return;
  13. }
  14. void timer_settime(struct TIMER *timer, unsigned int timeout)
  15. {
  16.         timer->timeout = timeout + timerctl.count;
  17.         timer->flags = TIMER_FLAGS_USING;
  18.         if (timerctl.next > timer->timeout) {
  19.                 /* 更新下一次的时刻 */
  20.                 timerctl.next = timer->timeout;
  21.         }
  22.         return;
  23. }
复制代码
这样就好了。现在我们来确认是否能正常运行。“makerun”。……和从前一样,固然仍不能
切身地感受到速度变快了,但还是自我满意一下吧。
3.0

到了harib09f的时候,中断处理程序的平均处理时间已经大大缩短了。这真是太好了。可是,
现在有一个问题,那就是到达next时刻和没到next时刻的定时器中断,它们的处理时间差别很大。
这样的程序布局欠好。由于寻常运行一直都很快的程序,会偶然由于中断处理拖得太长,而搞得像是主程序要停了似的。更确切一点,这样偶然会让人觉得“不知为什么,鼠标偶然会反应迟钝,很卡。”
因此,我们要让到达next时刻的定时器中断的处理时间再缩短一些。,怎么办呢?模仿
sheet.c的做法怎么样呢?我们来试试看。
在sheet.c的布局体struct SHTCTL中,除了sheet0[]以外,我们还定义了*sheets[]。它里面存
放的是按某种顺序排好的图层地址。有了这个变量,按顺序描绘图层就简单了。这次我们在stuct TIMERCTL中也定义一个变量,其中存放按某种顺序排好的定时器地址。
  1. struct TIMERCTL {
  2.         unsigned int count, next, using;
  3.         struct TIMER *timers[MAX_TIMER];
  4.         struct TIMER timers0[MAX_TIMER];
  5. };
复制代码
变量using相称于struct SHTCTL中的top,它用于记录现在的定时器中有几个处于运动中。
改进后的inthandler20函数如下:
  1. void inthandler20(int *esp)
  2. {
  3.         int i, j;
  4.         io_out8(PIC0_OCW2, 0x60);        /* 把IRQ-00信号接收结束的信息通知给PIC */
  5.         timerctl.count++;
  6.         if (timerctl.next > timerctl.count) {
  7.                 return;
  8.         }
  9.         for (i = 0; i < timerctl.using; i++) {
  10.                 /* timers的定时器都处于动作中,所以不确认flags */
  11.                 if (timerctl.timers[i]->timeout > timerctl.count) {
  12.                         break;
  13.                 }
  14.                 /* 超时 */
  15.                 timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
  16.                 fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
  17.         }
  18.         /* 正好有i个定时器超时了。其余的进行移位。 */
  19.         timerctl.using -= i;
  20.         for (j = 0; j < timerctl.using; j++) {
  21.                 timerctl.timers[j] = timerctl.timers[i + j];
  22.         }
  23.         if (timerctl.using > 0) {
  24.                 timerctl.next = timerctl.timers[0]->timeout;
  25.         } else {
  26.                 timerctl.next = 0xffffffff;
  27.         }
  28.         return;
  29. }
复制代码
这样,即使是在超时的情况下,也不用查找下一个next时刻,大概查找有没有别的定时器超
时了,真不错。如果有很多的定时器都处于正在实验的状态,我们会担心定时器因移位而变慢,这放在以后再改进吧。
由于timerctl中的变量名改变了,以是其他地方也要随之修改。
  1. void init_pit(void)
  2. {
  3.         int i;
  4.         io_out8(PIT_CTRL, 0x34);
  5.         io_out8(PIT_CNT0, 0x9c);
  6.         io_out8(PIT_CNT0, 0x2e);
  7.         timerctl.count = 0;
  8.         timerctl.next = 0xffffffff; /* 因为最初没有正在运行的定时器 */
  9.         timerctl.using = 0;
  10.         for (i = 0; i < MAX_TIMER; i++) {
  11.                 timerctl.timers0[i].flags = 0; /* 未使用 */
  12.         }
  13.         return;
  14. }
  15. struct TIMER *timer_alloc(void)
  16. {
  17.         int i;
  18.         for (i = 0; i < MAX_TIMER; i++) {
  19.                 if (timerctl.timers0[i].flags == 0) {
  20.                         timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
  21.                         return &timerctl.timers0[i];
  22.                 }
  23.         }
  24.         return 0; /* 没找到 */
  25. }
复制代码
这两个函数比较简单,只是稍稍修改了一下变量名。
在timer_settime函数中,必须将timer注册到timers中去,而且要注册到正确的位置。如果在注册时发生中断的话可就麻烦了,以是我们要事先关闭中断。
  1. void timer_settime(struct TIMER *timer, unsigned int timeout)
  2. {
  3.         int e, i, j;
  4.         timer->timeout = timeout + timerctl.count;
  5.         timer->flags = TIMER_FLAGS_USING;
  6.         e = io_load_eflags();
  7.         io_cli();
  8.         /* 搜索注册位置 */
  9.         for (i = 0; i < timerctl.using; i++) {
  10.                 if (timerctl.timers[i]->timeout >= timer->timeout) {
  11.                         break;
  12.                 }
  13.         }
  14.         /* i号之后全部后移一位 */
  15.         for (j = timerctl.using; j > i; j--) {
  16.                 timerctl.timers[j] = timerctl.timers[j - 1];
  17.         }
  18.         timerctl.using++;
  19.         /* 插入到空位上 */
  20.         timerctl.timers[i] = timer;
  21.         timerctl.next = timerctl.timers[0]->timeout;
  22.         io_store_eflags(e);
  23.         return;
  24. }
复制代码
这样做看来不错。固然中断处理程序速度已经进步了,但在设定定时器期间,我们关闭了中
这多少有些令人遗憾。不过就算对此不满意,也不要任意更改哦。
从某种水平上来讲,这也是无法制止的事。如果在设定时,多下点工夫整理一下,到达中断
时刻时就能轻松一些了。反之,如果在设定时偷点懒,那么到达中断时刻时就要吃点苦头了。总之,要么提前做好预备,要么暂时抱佛脚。毕竟哪种做法好呢,要根据情况而定。
总结

`
现在我们实验“make run”看看吧。盼望它能正常运行。会怎么样呢?貌似很顺利,太好了。
关于定时器我们另有想要修改的地方。不过大家肯定已经很困了,我们还是明天再继续吧。
再见!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4