30天自制操作系统(day12)

第12天:定时器(1)

1、内容1:使用定时器

定时器的意思是每隔一段时间就发送一个中断信号给CPU,这样CPU就不需要自己去计量时间。如果不采用定时器会导致程序对时间的管理难度增大,并且不能再使用HLT指令了。因此很有必要采用定时器中断,当使用定时器后,程序只需要以自己的步调处理问题即可,当需要知道时间时,只需要在中断程序中记录定时器中断发生的次数即可。即使CPU处于HLT状态时,也可以通过中断来唤醒。
那么如何才能管理定时器呢?
对PIT进行设定即可,PIT=可编程的间隔型定时器,通过设定,让定时器每隔多少秒产生一次中断。电脑中的PIT连接IRQ的0号,所以只要设定了PIT就可以设定IRQ0的中断间隔。
在这里插入图片描述
IRQ0的中断周期变更:
D0-D7就是我们的选择模式端
书本上介绍的往0x43中写入0x34对应的控制字格式:
在这里插入图片描述
写入的是0x34对应二进制为00110100,那么选择的模式就是计数器0,选择先低后高两个字节读或写,以方式2进行二进制计数。方式2在上面的文库中找到的模式是频率发生器。往0x40写入的是中断周期,书上的意思是写入的值代表了多少个时钟周期,芯片时钟频率为1193.18KHZ,那么中断频率=(单位时间时钟周期数(主频))/(设定的数值)。如果指定中断周期为0,看作指定为65536,如果设定值是1000,那么中断产生频率就是1.19318KHz,根据比例可以设定不同的值。
在这里插入图片描述
在这里,只需要执行3次OUT指令设定即可完成定时器的设定,如果将中断周期设为11932的话,中断频率即为100HZ,即1s发生100次中断。将11932换算为十六进制0x2e9c来进行设定。a的timer文件与bootpack.c中:
选择模式定义一个init_pit函数:
在这里插入图片描述
主函数中加入:
在这里插入图片描述
这样设定的话,IRQ0就会在1s内发生100次中断了。
当IRQ0发生时所调用的中断处理程序基本上和键盘中断处理程序一样。a的timer文件与naskfunc文件中:
在这里插入图片描述
在这里插入图片描述
因为要将中断处理程序注册到IDT中,因此inlt_gdtidt函数也要加上几段程序。
在这里插入图片描述

2、内容2:计量时间

要实现计量时间,只需要设置一个计数变量,当定时器发生中断时,计数变量就会以1递增,也就是没过1s,它会自动增加100,因为1s发生了100次中断。主要看b中的time文件:
在这里插入图片描述
然后通过屏幕显示出来:
在这里插入图片描述
实验结果:
在这里插入图片描述

3、内容3:超时功能

之前已经实现了计量时间了,现在可以进一步尝试,让定时器过了一段时间后发出提醒,实现超时功能。
书本上把定时(到一定时间就执行某事的功能)称为超时。
往结构体TIMERCTL中添加timeout,data,fifo记录超时信息。
Timeout:记录距离超时还有多长时间,一旦这个剩余时间达到0,程序就往FIFO缓冲区中发送数据,定时器边通过这样的方法来通知HariMain时间到了。
Data:到达时间后往缓冲区发生的数据
Fifo:缓冲区
在这里插入图片描述
在这里插入图片描述
在inthandler20函数(中断处理函数)里实现了超时功能,每次发生中断就把timeout减1,减到0时,就想fifo中发送数据。
在settimer函数中,如果设定还没有完全结束,IRQ0的中断就进来的话,会引起混乱,因此先禁止中断,然后完成设定,最后再把中断状态复原。bootpack.c文件中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当定时器、鼠标、键盘没有发生中断时,执行io_sti(),当定时器发生中断时,首先读入数据得到地址,然后执行io_sti(),在屏幕上显示10[sec],然后刷新图层。
实验结果:
在这里插入图片描述

4、内容4:使用多个定时器

在超时结束后再设定1000的话,就可以使显示变为10s依次,或是让其一闪一灭的显示,例如设定为0.5s的间隔可以用于文字输入时的光标闪烁。要实现多个这样的超时功能,便需要准备多个能设定超时的定时器。
首先是修改结构体,在e的bootpack.h文件中:
在这里插入图片描述
将能够设定的最多的超时定时器的数目设为500,flag则用于记录各个定时器的状态。同时,将其他地方的函数也做一定的修改,主要是bootpack.c文件。
这里讲一下每个函数的作用:
在这里插入图片描述
在主函数中写入:
在这里插入图片描述
实验结果:
在这里插入图片描述

5、内容5:加快中断处理(1)

虽然之前已经可以自由的使用多个定时器了,但是inthandler20还是有一定的问题,中断处理本来应该在很短的时间内完成,但利用inthandler20却花费了很长的时间,这样便会妨碍其他中断处理的执行,使得操作系统反应很迟钝。为什么呢?
因为在每次进行定时器中断处理的时候,都会对所有活动中的定时器进行timerctl.timer[i].timeout—处理。即CPU要完成从内存中读取变量值,减去1,然后又往内存中写入的操作。
要改善这样的状况,有一个比较简单的方法,就是修改变量timer[i].timeout的含义,使得其不再是所剩时间,而是指定时刻。现在的时刻计数到count中,然后将count和timeout进行比较,如果相同或者超过了,就代表定时器超时了,就通过向FIFO缓冲区里传送数据来通知HariMain,这样的话,程序的速度便会有一定的提升。
实际上就是每次count++的时候判断一次,是否有定时器到达时间,如果到达就把数据写入缓冲区,并把定时器变成已配置状态。虽然这里用不到内存操作了,但是每次计时都要判断500次,也花费了很多时间。
同时修改timer_settime函数,该函数中所指定的时间,是从现在开始多少多少秒以后的意思,因此用这个时间加上现在的时刻,便可以计算出中断的预定时刻,程序中对整个时刻进行记录。这样设定之后启动以后经过42949673秒以后count就变成0XFFFFFFFF,不能再大了,达到了能承受的界限。
在这里插入图片描述
针对这个问题,作者提供了一种调整时刻的思路,大致意思就是设置一个t0的值,每个时刻都减去这个值,比如说当t0=1000s时,所有时刻减去1000,假设count减少1000后的值是1,表示实际时间为1001,但是显示出来的是1,这样起到一个延长时间的作用。对于这个我并没有写入代码。
在这里插入图片描述

6、内容6:加快中断处理(2)

虽然之前已经使得程序的速度有了一定的提升,但是还是有些慢,为什么会这样呢?因为在inthandler20中,每次中断都要都要执行500次if语句进行判断,由于1s中就会发生100次中断,if语句就会1s中执行5万次,十分的浪费时间。
在这里,我们只需要判断下一次时刻有没有到达即可,不需要将500个全部判断完。因此追加一个变量timectl.next,让它记住下一个时刻,到达之后就往后移一个定时器,直到全部定时器完成。f中的bootpack.h文件与timer文件
在这里插入图片描述
在这里插入图片描述
先对下一个定时器进行判断,如果没到时间(next>count),直接跳出,如果到了时间,就对运行中的定时器进行判断,如果时间到了就把数据存入缓冲区,如果没到时间,把next移向时间最短的那个定时器,找到比当前的next时间小的就替换next。这样便减少了无意义的if语句的执行次数,速度也会快不少。

7、内容7:加快中断处理(3)

之前的优化会导致一个问题,就是到达next时刻和没到next时刻的定时器中断,它们的处理时间差别很大,有一个到达next时刻和没到next时刻的定时器速度不一致问题。因此需要让到达next时刻的定时器中断处理时间再缩短一些。因此便模仿sheet.c中的做法,在stuct TIMERCTL中定义一个变量,其中存放按某种顺序排好的定时器地址。g中的bootpack.h文件
在这里插入图片描述
变量using想防御stuct SHTCTL中的top,用于记录现在的定时器中有几个处于活动中。
在这里插入图片描述
这里减少了确认flags的判断,直接对using中的定时器进行循环判断是否超时,如果有i超时了,那么using-i,其余的定时器进行移位timerctl.timers[j] = timerctl.timers[i + j];后面的定时器前移i位,如果还有定时器在使用,就把第一个的定时器的值存到next中,这样就不用每次都寻找下一个next时刻。
在这里插入图片描述
在settime函数中,需要将timer注册到timers中,首先找到注册的位置,关闭中断,然后将该注册位置后的所有定时器号往后移一位,为该定时器腾出一个位置,然后using++,将该定时器插到空位上。
后一种方法与前一种方法的区别在于,settime的时候对定时器排序,而使用next的方法在中断处理的时候排序,这样对操作系统来说当然是排序好之后再计时速度快一些。

发布了205 篇原创文章 · 获赞 110 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_40851744/article/details/103440157