30天开发操作系统 第 12 天 -- 定时器 v1.0
前言
定时器(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。
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040void init_pit(void)
{io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);return;
}
void HariMain(void)
{...init_gdtidt();init_pic();io_sti(); /* IDT/PIC的初始化已经结束,所以解除CPU的中断禁止 */fifo8_init(&keyfifo, 32, keybuf);fifo8_init(&mousefifo, 128, mousebuf);init_pit(); /* 这里 */...
}
这样的话IRQ0就会在1秒钟内发生100次中断了。
下面我们来编写IRO0发生时所调用的中断处理程序。它几乎和键盘中断处理程序一样,大家还记得吗:
void inthandler20(int *esp)
{io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC *//* 暂时什么也不做 */return;
}
我们把 init_pit 和 inthandler20 放到了新创建的文件夹 timer.c
下面是naskfunc.nas新增的:
_asm_inthandler20:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler20POP EAXPOPADPOP DSPOP ESIRETD
大家还记得下一步是要干什么吗?
为了把这个中断处理程序注册到IDT,inlt gdtidt函数中也要加上几行。这也和键盘处理的时候差不多哦:
/* IDT的设定 */set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
到这里准备工作就完成了。也不知能不能正常运行。正常的话,嗯,应该什么都不发生。
下面我们执行“make run”。哦,什么也没发生。太好了!但这样有点不过瘾,还是在中断处理程序中做点什么吧!
2.计量时间
那我们让它干点什么呢?……我们就让它执行下面这段程序吧:
bookpack.h
struct TIMERCTL {unsigned int count;
};
#include "bootpack.h"#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040struct TIMERCTL timerctl;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-00信号接收完了的信息通知给PIC */timerctl.count++; /* 这里 */return;
}
大家瞅瞅,没几行新增代码。程序所做的处理是:首先定义了struct TIMERCTL结构体。然后,在结构体内定义了一个计数变量 count。初始化PIT时,将这个计数变量设置为0。每次发生定时器中断时,计数变量就以1递增。
也就是说,即使这个计数变量在HariMain中不进行加算,每1秒钟它也会自动增加100。
为了确认,我们把数值显示出来吧。
...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);...
这样的话,数字应该是以每秒钟100的速度增加。而且不论哪个机种增加速度都是一样的。
即使CPU的速度不同,增加速度也应该是一样的。我们先做做看吧。执行“make run"。……正常运行了,还算顺利。
就能知道从启动开始时间过去了多少秒。如果往方便面里倒入开水的同时利用这个方法,就能测量是否到3分钟(=180秒)了。哦,终于向着有实用价值的操作系统迈出了第一步。
现在,从启动开始经过了多少秒这一类问题,我们就可以很轻松地判断了。另外,我们还可以计量处理所花费的时间。具体做法是,处理前看一下时间并把它存放到一个变量里,处理结束之后再看一下时间,然后只要用减法算出时间差,就能得到答案了,比如“这个处理耗时13.56秒”等。我们甚至可以据此编制基准测试程序(benchmark program)。
这里大家稍稍回想一下,现在已经能够显示出窗口,又能使用鼠标,又能计量时间,还能进行内存管理,已经实现了很多功能。有了这些功能,只要对它们进行各种组合,就能做很多事情。
我们言归正传,继续说定时器吧。操作系统的定时器经常被用于这样一种情形:“喂,操作系统,过了10秒钟以后通知我一声,我要干什么什么”。当然,不一定非要是10秒,也可以是1秒或30分钟。我们把这样的功能叫做“超时”(timeout)。下面就来实现这个功能吧。
首先往结构体struct TIMERCTL里添加一些代码,以便记录有关超时的信息。
struct TIMERCTL {unsigned int count;unsigned int timeout;struct FIFO8 *fifo;unsigned char data;
};
以上结构体中的timeout用来记录离超时还有多长时间。一旦这个剩余时间达到0,程序就往FIFO缓冲区里发送数据。定时器就是通过这种方法通知HariMain时间到了。至于为什么要使用FIFO缓冲区,也说不上个所以然,只是觉得这个方法简单,因为使用FIFO缓冲区来通知的话,可以比照键盘和鼠标,利用同样的方法来处理。
下面我们来修改函数吧。
#include "bootpack.h"#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040struct 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 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;
}
希望大家注意的是,我们在inthandler20函数里实现了超时功能。每次发生中断时就把timeout减1,减到0时,就向fifo发送数据。
在settimer函数里,如果设定还没有完全结束IRQ0的中断就进来的话,会引起混乱,所以我们先禁止中断,然后完成设定,最后再把中断状态复原。
这在HariMain中如何实现呢?我们来尝试这样做:
void HariMain(void)
{struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;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) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 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);}}}
}
程序很简单,我们在其中设定10秒钟以后向timerfifo写人“1”这个数据,而timerffo接收到数据时,就会在屏幕上显示“10[sec]”。
我们执行一下“make run”,看,显示出来了!
2.设定多个计时器
在上一节做的超时功能,超时结束后如果再设定1000的话,那我们就可以让它每10秒显示或是让它一闪一灭地显示。另外,间隔不仅限于10秒,我们还可以设定得更长一些或更短一次,些。比如设定为0.5秒的间隔可以用于文字输人时的光标闪烁。
开发操作系统时,超时功能非常方便,所以在很多地方都可以使用它。比如可以让电子时钟每隔1秒重新显示一次;演奏音乐时,可以用它计量音符的长短;也可以让它以0.1秒1次的频率来监视没有中断功能的装置;另外,还可以用它实现光标的闪烁功能。
为了简单地实现这些功能,我们要准备很多能够设定超时的定时器。
首先把struct TIMERCTL修改成下面这样。
#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];
};
这样超时定时器最多就可以设定为500个了,fags则用于记录各个定时器的状态。
继续修改对应的函数:
#include "bootpack.h"#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040struct TIMERCTL timerctl;#define TIMER_FLAGS_ALLOC 1 /* 已配置状态 */
#define TIMER_FLAGS_USING 2 /* 定时器运行中 */void init_pit(void)
{int i;io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;for (i = 0; i < MAX_TIMER; i++) {timerctl.timer[i].flags = 0; /* 未使用 */}return;
}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)
{int i;io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */timerctl.count++;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;
}
程序稍微有些长,不是很难,只要前面的程序大家都明白了,这里应该也没什么困难。
最后来看HariMain函数。我们不一定都设定为10秒,也尝试一下设为3秒吧。另外,我们还要编写类似光标闪烁那样的程序。
void HariMain(void)
{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;...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 (;;) {...io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo)+ fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) {io_sti();} else {if (fifo8_status(&keyfifo) != 0) {...} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 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.0
现在我们可以自由使用多个定时器了,从数量上说,已经足够了。但仔细看一下大家会发现,inthandler20还有很大问题:中断处理本来应该在很短的时间内完成,可利用inthandler20时却花费了很长时间。这就妨碍了其他中断处理的执行,使得操作系统反应很迟钝。
如果检査inthandler20,能发现每次进行定时器中断处理的时候,都会对所有活动中的定时器
进行“timerctl.timer[i].timeout–;”处理。也就是说,CPU要完成从内存中读取变量值,减去1,然后又往内存中写人的操作。本来谁也不会注意到这种细微之处,但由于我们想在中断处理程序中尽可能减少哪怕是一点点工作量,所以才会注意到这里。
问题找到了,那该怎么修改才好呢?我们看看下面这样行不行:
void inthandler20(int *esp)
{int i;io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */timerctl.count++;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {if (timerctl.timer[i].timeout <= timerctl.count) {timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);}}}return;
}
我们改变了程序中变量timer[i].timeout的含义。它指的不再是“所剩时间”,而是“设定时刻”
了。因为现在的时刻计数到timerctl.count中去了,所以就拿它和timer[i].timeout进行比较,如果相同或是超过了,就通过往FIFO缓冲区里传送数据来通知HariMain。大家现在再看一看,我们一直担心的减法计算没有了。这样一改,程序的速度应该能稍微变快一些了。
下面我们也要相应地修改timer_settime函数。
void timer_settime(struct TIMER *timer, unsigned int timeout)
{timer->timeout = timeout + timerctl.count;timer->flags = TIMER_FLAGS_USING;return;
}
timer_settime函数中所指定的时间,是“从现在开始多少多少秒以后”的意思,所以用这个
时间加上现在的时刻,就可以计算出中断的预定时刻。程序中对这个时刻进行了记录。别的地方就不用改了。
到底这样做行不行呢,我们执行一下“make run”。好哇,进行得很顺利。虽然还没能切身
感到速度变快了多少,不过先自我满足一下吧。
同时也正是因为变成了这种方式,在我们这个操作系统中,启动以后经过42949673
秒后,count就是0xfffffffff了,比这个值再大就不能设定了。这么多秒是几天呢?……嗯,请稍等(用计算器算一下)……大约是497天。也就是大约一年就要重新启动一次操作系统,让count归0。
这里大家可能又会有怨言了“哎呀,还需要重新起动,这样的操作系统真是麻烦”。事实上
本人也是这么想的(笑)。怎么办才好呢。回到上一节的做法,好不好呢?可是回到上
一节的做法,速度又有些慢。……既不希望速度慢,又不想重新启动 – 为了满足这种奢望,我们设计成一年调整一次时刻的程序也许比较好。
int t0=timerctl.count;/*所有时刻都要减去这个值*/
io_cli();/*在时刻调整时禁止定时器中断 */
timerctl.count-=t0;
for(i=0;i<MAX TIMER;i++){if(timerctl.timeri],flags ==TIMER FLAGS USING){timerctl.timer[i].timeout =t;}
}
io_sti();
也许以上方法并非最好,但我们不轻言放弃而去想办法解决,这种心境是最重要的。只要努
力,我们肯定还能找到别的好办法。
2.0
我们再来改善一下吧。
代码如下(示例):
void inthandler20(int *esp)
{int i;io_out8(PIC0_OCW2, 0x60);timerctl.count++;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {if (timerctl.timer[i].timeout <= timerctl.count) {timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);}}}return;
}
如果看一下 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,让它记住下一个时刻。
struct TIMERCTL {unsigned int count, next;struct TIMER timer[MAX_TIMER];
};
void inthandler20(int *esp)
{int i;io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */timerctl.count++;if (timerctl.next > timerctl.count) {return; /* 还不到下一个时刻,所以结束 */}timerctl.next = 0xffffffff;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {if (timerctl.timer[i].timeout <= timerctl.count) {/* 超时 */timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);} else {/* 还没有超时的话,这里会找到离下一次超时最近的*/if (timerctl.next > timerctl.timer[i].timeout) {timerctl.next = timerctl.timer[i].timeout;}}}}return;
}
虽然程序变长了,但要做的处理却减少了。在大多数情况下,第一个if语句的return都会执行,
中断处理就到此结束了。当到达下一个时刻时,使用之前那种方法检查是否超时。超时的话,就写人到FIFO中;还没超时的话就调查是否将其设定为下一个时刻(未超时时刻中,最小的时刻是下一个时刻)。
如果用这样的方法,就能大大减少没有意义的if语句的执行次数,速度也应该快多了。
由于使用了next,所以其他地方也要修改一下。
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; /* 因为最初没有正在运行的定时器 */for (i = 0; i < MAX_TIMER; i++) {timerctl.timer[i].flags = 0; /* 没有使用 */}return;
}void timer_settime(struct TIMER *timer, unsigned int timeout)
{timer->timeout = timeout + timerctl.count;timer->flags = TIMER_FLAGS_USING;if (timerctl.next > timer->timeout) {/* 更新下一次的时刻 */timerctl.next = timer->timeout;}return;
}
这样就好了。现在我们来确认是否能正常运行。“makerun”。……和以前一样,虽然仍不能
切身地感受到速度变快了,但还是自我满足一下吧。
3.0
到了harib09f的时候,中断处理程序的平均处理时间已经大大缩短了。这真是太好了。可是,
现在有一个问题,那就是到达next时刻和没到next时刻的定时器中断,它们的处理时间差别很大。
这样的程序结构不好。因为平常运行一直都很快的程序,会偶尔由于中断处理拖得太长,而搞得像是主程序要停了似的。更确切一点,这样有时会让人觉得“不知为什么,鼠标偶尔会反应迟钝,很卡。”
因此,我们要让到达next时刻的定时器中断的处理时间再缩短一些。,怎么办呢?模仿
sheet.c的做法怎么样呢?我们来试试看。
在sheet.c的结构体struct SHTCTL中,除了sheet0[]以外,我们还定义了*sheets[]。它里面存
放的是按某种顺序排好的图层地址。有了这个变量,按顺序描绘图层就简单了。这次我们在stuct TIMERCTL中也定义一个变量,其中存放按某种顺序排好的定时器地址。
struct TIMERCTL {unsigned int count, next, using;struct TIMER *timers[MAX_TIMER];struct TIMER timers0[MAX_TIMER];
};
变量using相当于struct SHTCTL中的top,它用于记录现在的定时器中有几个处于活动中。
改进后的inthandler20函数如下:
void inthandler20(int *esp)
{int i, j;io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给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 = 0xffffffff;}return;
}
这样,即使是在超时的情况下,也不用查找下一个next时刻,或者查找有没有别的定时器超
时了,真不错。如果有很多的定时器都处于正在执行的状态,我们会担心定时器因移位而变慢,这放在以后再改进吧。
由于timerctl中的变量名改变了,所以其他地方也要随之修改。
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;
}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; /* 没找到 */
}
这两个函数比较简单,只是稍稍修改了一下变量名。
在timer_settime函数中,必须将timer注册到timers中去,而且要注册到正确的位置。如果在注册时发生中断的话可就麻烦了,所以我们要事先关闭中断。
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;
}
这样做看来不错。虽然中断处理程序速度已经提高了,但在设定定时器期间,我们关闭了中
这多少有些令人遗憾。不过就算对此不满意,也不要随便更改哦。
从某种程度上来讲,这也是无法避免的事。如果在设定时,多下点工夫整理一下,到达中断
时刻时就能轻松一些了。反之,如果在设定时偷点懒,那么到达中断时刻时就要吃点苦头了。总之,要么提前做好准备,要么临时抱佛脚。究竟哪种做法好呢,要根据情况而定。
总结
`
现在我们执行“make run”看看吧。希望它能正常运行。会怎么样呢?貌似很顺利,太好了。
关于定时器我们还有想要修改的地方。不过大家肯定已经很困了,我们还是明天再继续吧。
再见!
相关文章:
![](https://i-blog.csdnimg.cn/direct/22456d20c2164811bab1d7b50a65bf5b.png)
30天开发操作系统 第 12 天 -- 定时器 v1.0
前言 定时器(Timer)对于操作系统非常重要。它在原理上却很简单,只是每隔一段时间(比如0.01秒)就发送一个中断信号给CPU。幸亏有了定时器,CPU才不用辛苦地去计量时间。……如果没有定时器会怎么样呢?让我们想象一下吧。 假如CPU看不到定时器而仍想计量时…...
![](https://www.ngui.cc/images/no-images.jpg)
Ubuntu | PostgreSQL | 解决 ERROR: `xmllint` is missing on your system.
解决 sudo apt install apt-file sudo apt-file updatesudo apt-file search xmllint sudo apt install libxml2-utils执行 # postgres源码安装包解压文件夹中 make install make install问题 make -C src install make[2]: Entering directory /home/postgres/postgresql-1…...
![](https://i-blog.csdnimg.cn/direct/5dfd114a2b1c4512ae15149d2806c3df.png)
uniapp使用chooseLocation安卓篇
本文章全部以高德地图为例 代码 <view class"bottom"><button click"choose">定位</button> </view> choose() {uni.chooseLocation({success: function(res) {console.log(位置名称: res.name);console.log(详细地…...
![](https://www.ngui.cc/images/no-images.jpg)
《PC 上的开源神经网络多模态模型:开启智能交互新时代》
《PC 上的开源神经网络多模态模型:开启智能交互新时代》 一、引言二、多模态模型基础剖析(一)核心概念解读(二)技术架构探秘 三、开源多模态模型的独特魅力(一)开源优势尽显(二&…...
![](https://i-blog.csdnimg.cn/direct/0a47b689d0a2499ab67fbc7e3a377fd7.png)
Apache JMeter 压力测试使用说明
文章目录 一、 安装步骤步骤一 下载相关的包步骤二 安装 Jmeter步骤三 设置 Jmeter 工具语言类型为中文 二、使用工具2.1 创建测试任务步骤一 创建线程组步骤二 创建 HTTP 请求 2.2 配置 HTTP 默认参数添加 HTTP消息头管理器HTTP请求默认值 2.3 添加 查看结果监听器2.4 查看结果…...
![](https://i-blog.csdnimg.cn/direct/9787647fc3ef47d09987f4ceb6202b13.png)
腾讯云AI代码助手编程挑战赛-知识百科AI
作品简介 知识百科AI这一编程主要用于对于小朋友的探索力的开发,让小朋友在一开始就对学习具有探索精神。在信息化时代下,会主动去学习自己认知以外的知识,同时丰富了眼界,开拓了新的知识。同时催生了在大数据时代下的信息共享化…...
![](https://i-blog.csdnimg.cn/direct/ad9f17b3ac6f40f988fa8c885e5e6163.png)
【SpringAOP】Spring AOP 底层逻辑:切点表达式与原理简明阐述
前言 🌟🌟本期讲解关于spring aop的切面表达式和自身实现原理介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 &am…...
![](https://i-blog.csdnimg.cn/direct/d356be18697e439db09e902751d2b9ac.png)
HTTP-响应协议
HTTP的响应过程? 浏览器请求数据--》web服务器过程:请求过程 web服务器将响应数据-》到浏览器:响应过程 响应数据有哪些内容? 1.和请求数据类似。 2. 响应体中存储着web服务器返回给浏览器的响应数据。并且注意响应头和响应体之间…...
![](https://www.ngui.cc/images/no-images.jpg)
SQL进阶实战技巧:即时订单比例问题
目录 0 需求描述 1 数据准备 2 问题分析 3 小结 往期精彩 0 需求描述 订单配送中,如果期望配送日期和下单日期相同,称为即时订单,如果期望配送日期和下单日期不同,称为计划订单。 请从配送信息表(delivery_info)中求出每个用户的首单(用户的第一个订单)中即时订单…...
![](https://www.ngui.cc/images/no-images.jpg)
什么是端口
端口是用来区分同一网络设备(IP地址)上运行的不同服务或应用程序接收外部数据的窗口。 以下是几个要点: 对于我们发送请求指定的url中的端口,指的是对方服务器的用于接收数据的端口,如http的80端口,服务器通常都会设定要监听来自…...
![](https://www.ngui.cc/images/no-images.jpg)
【Flutter】使用ScrollController配合EasyRefresh实现列表预加载:在还未滑动到底部时加载下一页数据
需求/背景 在我们的业务场景中,列表的加载使用easy_refresh组件: https://pub.dev/packages/easy_refresh 大概效果是往上滑动到一定的offset会触发一个上滑加载,可以触发一些网络请求拉取列表后面的数据来展示。 这种模式一般在一页翻完…...
![](https://www.ngui.cc/images/no-images.jpg)
【2025 Rust学习 --- 11 实用工具特型01】
清理特型Drop 当一个值的拥有者消失时,Rust 会丢弃(drop)该值。丢弃一个值就必须释放 该值拥有的任何其他值、堆存储和系统资源。 丢弃可能发生在多种情况下: 当变量超出作用域时;在表达式语句的末尾;当…...
![](https://www.ngui.cc/images/no-images.jpg)
网络安全基础以及概念
1. 安全领域的概念 1.1 网络产品 1. EDR:终端检测与响应(Endpoint Detection and Response),终端主要包括我们的笔记本、台式机、手机、服务器等,EDR是一种运行在终端上安全软件,主要负责监控网络流量、可疑进程、注册表活动等其他安全相关的事件与活动。当发现有威胁是自…...
![](https://i-blog.csdnimg.cn/direct/797b5b8fa9ee493ca6dfd10d5737dfb9.png)
windows和linux的抓包方式
1.实验准备: 一台windows主机,一台linux主机 wireshark使用: 打开wireshark,这些有波动的就代表可以有流量经过该网卡,选择一张有流量经过的网卡 可以看到很多的流量,然后可以使用过滤器来过滤想要的流量…...
![](https://i-blog.csdnimg.cn/direct/422ead50044540f398efc9f2ea3a543a.png)
【Uniapp-Vue3】v-if条件渲染及v-show的选择对比
如果我们想让元素根据响应式变量的值进行显示或隐藏可以使用v-if或v-show 一、v-show 另一种控制显示的方法就是使用v-show,使用方法和v-if一样,为true显示,为false则不显示。 二、v-if v-if除了可以像v-show一样单独使用外,还…...
![](https://www.ngui.cc/images/no-images.jpg)
宝塔面板使用 GoAccess Web 日志分析教程
宝塔面板是一个简单方便的服务器运维面板,但其网站统计功能是收费的。而 GoAccess 是一个用 C 编写的免费开源 Web日志分析器,本文将介绍如何在宝塔面板中开启 GoAccess Web 日志分析功能。 内容索引 下载安装 GoAccess在宝塔面板中添加日志切割的计划任务将 Web 日志输出到…...
![](https://i-blog.csdnimg.cn/img_convert/38dc07ec26712c3806fc49a8d21e27c9.png)
Windows 安装 Docker 和 Docker Compose
🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template 🌺 仓库主页: GitCode︱ Gitee ︱ Github 💖 欢迎点赞 👍 收藏 ⭐评论 …...
![](https://www.ngui.cc/images/no-images.jpg)
arcgis中用python脚本批量给多个要素类的相同字段赋值
1、python脚本 import arcpy# 设置工作空间路径 arcpy.env.workspace = r"D:\test.gdb"# 要素集名称 feature_dataset = "test"# 线要素类名称列表,初始化为空 line_feature_classes = []# 遍历要素集获取所有线要素类 for fc in arcpy.ListFeatureClass…...
![](https://i-blog.csdnimg.cn/direct/d3474bf240ae4c298b0dfda8e8b2fc5a.png)
目标客户营销(ABM)结合开源AI智能名片2+1链动模式S2B2C商城小程序的策略与实践
摘要:在数字化营销日益盛行的今天,目标客户营销(Account Based Marketing, ABM)作为一种高度定制化的营销策略,正逐步成为企业获取高质量客户、提升市场竞争力的重要手段。与此同时,开源AI智能名片21链动模…...
![](https://i-blog.csdnimg.cn/direct/01ec1ce78b3b4197a7f2586861b47461.png)
《异步编程之美》— 全栈修仙《Java 8 CompletableFuture 对比 ES6 Promise 以及Spring @Async》
哈喽,大家好!在平常开发过程中会遇到许多意想不到的坑,本篇文章就记录在开发过程中遇到一些常见的问题,看了许多博主的异步编程,我只能说一言难尽。本文详细的讲解了异步编程之美,是不可多得的好文…...
![](https://www.ngui.cc/images/no-images.jpg)
新模型设计:Hybrid Quantum-Classical Neural Network (HQCNN) for Image Classification
新模型设计:Hybrid Quantum-Classical Neural Network (HQCNN) for Image Classification 目录 新模型设计:Hybrid Quantum-Classical Neural Network (HQCNN) for Image Classification引言1. Hybrid Quantum-Classical Neural Network 简介2. Hybrid Quantum-Classical Neu…...
![](https://i-blog.csdnimg.cn/direct/8304d00a6e6146c5bb0628b5c0d03910.png)
iOS 中spring动画的使用
我们先来看以下两个动画的效果 上面的位移动画,一个是普通的动画,一个是spring动画,可以明显的看出来,spring动画在动画的前期更快一些,给人的感觉干脆,利落 以下是代码 - (void)normalAnimation {[UIV…...
![](https://i-blog.csdnimg.cn/direct/6ad8ec8ff29c4003866d55533e9bd38f.png)
初学stm32 --- DMA直接存储器
目录 DMA介绍 STM32F1 DMA框图 DMA处理过程 DMA通道 DMA优先级 DMA相关寄存器介绍 F1 DMA通道x配置寄存器(DMA_CCRx) DMA中断状态寄存器(DMA_ISR) DMA中断标志清除寄存器(DMA_IFCR) DMA通道x传输…...
![](https://www.ngui.cc/images/no-images.jpg)
校医院挂号及预约 APP 的设计与实现
标题:校医院挂号及预约 APP 的设计与实现 内容:1.摘要 随着移动互联网的发展,越来越多的人开始使用手机应用程序来解决生活中的各种问题。本项目旨在设计和实现一款校医院挂号及预约 APP,以提高校医院的服务效率和质量,方便师生就医。本文介…...
![](https://www.ngui.cc/images/no-images.jpg)
代理模式详解与应用
代理模式(Proxy Pattern),也称为委托模式或 surrogate 模式,是一种结构型设计模式。它为其他对象提供一个代理以控制对这个对象的访问。通过引入代理对象,可以在不改变原始对象接口的前提下,添加额外的功能…...
![](https://i-blog.csdnimg.cn/img_convert/6349de24781705c4aad44b683d08b5d2.gif)
Model-based RL自动出价算法的演进之路
▐ 导读 近年来,强化学习自动出价算法已成为智能投放领域的标志性技术,然而其所存在的在离线不一致、线上数据覆盖空间受限等关键问题尚未被完全解决。在本文中,我们提出一种Model-based RL(MBRL)自动出价算法训练新范…...
![](https://i-blog.csdnimg.cn/direct/189d207be6ab4cf2858e4b16257df707.png)
.NET AI 开发人员库 --AI Dev Gallery简单示例--问答机器人
资源及介绍接上篇 nuget引用以下组件 效果展示: 内存和cpu占有: 代码如下:路径换成自己的模型路径 模型请从上篇文尾下载 internal class Program{private static CancellationTokenSource? cts;private static IChatClient? model;privat…...
![](https://i-blog.csdnimg.cn/img_convert/028ebd72f284dfab26bfbcdbc268f584.png)
框架部分面试题学习
IOC容器,AOP IOC :依赖反转,将对象的创建,组装,管理的控制权限从应用程序反转到IOC容器中。由springboot的来实现对象的自动装配和注入。 当某个类使用了Componnet 注解后,标记为一个组件。那么这个类在项…...
![](https://i-blog.csdnimg.cn/direct/f06b4cb459e943c7b5c24afe214fc9fb.png)
tdengine数据库使用java连接
1 首先给你的项目添加依赖 <dependency> <groupId>com.taosdata.jdbc</groupId> <artifactId>taos-jdbcdriver</artifactId> <version>3.4.0</version> <!-- 表示依赖不会传递 --> </dependency> 注意&am…...
![](https://www.ngui.cc/images/no-images.jpg)
Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)
Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser) 思路字符串替换器 思路 模板变量替换无非是寻找出字符串(模板)中的特殊标记,用对应的变量进行字符串替换。 提到变量替换,大家第一能…...
![](https://img-blog.csdnimg.cn/20200815233807616.png#pic_center)
一个网站做app/爱站权重查询
Linux之ARM(IMX6U)BSP工程管理实验1、工程管理简介1.1、创建bsp、imx6ul、obj和project这四个文件夹1.2、文件分类2、实验程序编写2.1、创建 imx6ul.h 文件2.2、创建个.vscode文件修改includePath2.2.1、修改includePath2.3、编写led驱动文件2.2.1、 bsp…...
![](/images/no-images.jpg)
书籍网站建设的目的/企业培训课程清单
case:Spark向kafka中写入数据 对于每个partition的每条记录,我们都需要创建KafkaProducer,然后利用producer进行输出操作,注意这里我们并不能将KafkaProducer的新建任务放在foreachPartition外边,因为KafkaProducer是不可序列化的…...
![](/images/no-images.jpg)
wordpress 主页幻灯片/做网站多少钱一年
本发明涉及一种基于Kaa服务的跨平台日志采集方法。背景技术:物联网是在互联网基础上的延伸和扩展的网络,其用户端延伸和扩展到了任何物品与物品之间,进行信息交换和通信,也就是物物相息,随着物联网技术快速发展&#x…...
wordpress的qq邮件列表qq邮件列表订阅rss源地址怎么找/企业网络营销方案策划
快速开发平台,简单地说就是指那些不用编码或通过少量代码,就可以快速开发应用程序的平台。既可以降低开发人力成本,又可以缩短开发时间,从而实现企业降本增效的价值。应用快速开发(rapid application development, RAD…...
![](https://img-blog.csdnimg.cn/20181122104410235.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F4aWFvYm9nZQ==,size_16,color_FFFFFF,t_70)
商洛网站设计/怎么让网站被百度收录
之前提到java.util.concurrent包中的原子类,就是通过CAS来实现了乐观锁,那么我们进入原子类AtomicInteger的源码,看一下AtomicInteger的定义: 根据定义我们可以看出各属性的作用: unsafe: 获取并操作内存的…...
![](https://img-blog.csdnimg.cn/img_convert/29517c21a7d6cee34a59b2647a64b892.gif)
可以免费做推广的网站/百度app下载安装普通下载
XD中的图层面板Adobe XD里画出来的每一个元素都是一个独立的图层,和PS一样可以通过快捷键CtrlG键进行自由组合,也可以通过CtrlShiftG取消组合。单击工具栏中的“图层”工具,或者按CtrlY键打开和关闭图层面板。在未选择任何对象的情况下&#…...