30天自制操作系统第12、13天
操作系统实验日志12 13
第12、13天:定时器
30天自制操作系统第12、13天
一、实验主要内容
1、 内容1:使用定时器
想要使用定时器就要对PIT进行设置,因为PIT连着IRQ0,所以设定了PIT就可以设定IRQ0的中断间隔。
需要执行3次OUT指令:
中断频率=(单位时间时钟周期数(主频))/(设定的数值),依此可以根据需求设置数值。
根据作者电脑的主频,设定值为11923=0x2e9c时,每10ms发生依次中断,对应的代码为:
因为PIT连着IRQ0,所以我们要为IRQ0产生的中断写一个中断函数:
然后要将中断处理程序注册到IDT中:
2、 内容2:计量时间
这里就是使用一个count变量计数,IRQ0处每发生一次中断,就令count加一,所以每隔1s,count会增加100.
3、 内容3:超时功能
使用定时器来记录时间,时间到了就发送一个特定的数据到一个fifo缓冲区中。
Timeout:记录距离超时还有多长时间,一旦这个剩余时间达到0,程序就往FIFO缓冲区中发送数据。
Data:到达时间后往缓冲区发生的数据。
Fifo:存放数据的缓冲区
在入口函数中:
所以到了10s,屏幕上就会显示10[sec].
4、 内容4:使用多个定时器
虽然小标题说使用多个定时器,其实并不指的是我们真正使用了多个定时器,我们仅仅使用了一个定时器,而且是每10ms中断一次。这里说的是我们可以利用这个定时器来设置多个时间,比如说开始计时的时候,10s时发送一个数据到fifo,然后25s时又发送了另一个数据到fifo。
①首先要用一个数组管理多个需要定时的时间。
②然后对他们进行初始化。
③
④已经申请一个定时器后,开始使用的话,就需要设置时间
⑤使用多个“定时器“,中断函数也会发生变化
⑥最后,我们可以根据fifo中的数据,知道过了多久从而实现想要实现的功能,比如实现光标的闪动。
5、 内容5:加快中断处理1
利用inthandler20却花费了很长的时间。因为在每次进行定时器中断处理的时候,都会对所有活动中的定时器进行timerctl.timer[i].timeou–处理。即CPU要完成从内存中读取变量值,减去1,然后又往内存中写入的操作。
因此可以修改变量timer[i].timeout的含义,使得其不再是所剩时间,而是指定时刻。现在的时刻计数到count中,然后将count和timeout进行比较,如果相同或者超过了,就代表定时器超时了,就通过向FIFO缓冲区里传送数据来通知HariMain。
同时要修改timer_settime函数,该函数中所指定的时间,是从现在开始多少多少秒以后的意思,因此用这个时间加上现在的时刻,便可以计算出中断的预定时刻,程序中对整个时刻进行记录。
这样设定之后启动以后经过42949673秒以后count就变成0XFFFFFFFF,不能再大了,达到这个值就要重置一下count值。
6、 内容6:加快中断处理2
虽然之前已经使得程序的速度有了一定的提升,但是还是有些慢,因为在inthandler20中,每次中断都要都要执行500次if语句进行判断,由于1s中就会发生100次中断,if语句就会1s中执行5万次,十分的浪费时间。
因为我们现在的timeout记录的是时刻,所以我们只需要判断下一次时刻有没有到达即可,不需要将500个全部判断完。因此追加一个变量timectl.next来记住下一个时刻。
在中断函数中,先判断下一个定时的时刻是否到了,如果没到时间(next>count),就return结束。如果到了时间,就对运行中的定时器进行判断,如果时间到了就把数据存入缓冲区,如果没到时间,找到比当前的next时间小的就替换next。
注意当新增定时器的时候,如果next大于其timeout的值,要及时更改next的值,不然在中断函数中,就会跳过该定时器。
7、 内容7:加快中断处理3
当当前的定时器时间到了之后,我们需要定位到是哪个定时器,然后还要找到下一个next时刻,这就需要我们的for循环循环500遍。所以不如我们维护一个排好序的数组,减少不必要的判断。
在stuct TIMERCTL中定义一个变量,其中存放按某种顺序排好的定时器地址:
其他函数改变一下变量名。
在settime函数中,需要将timer注册到timers中,首先找到注册的位置,关闭中断,然后将该注册位置后的所有定时器号往后移一位,为该定时器腾出一个位置,然后using++,将该定时器插到空位上。
8、内容8:简化字符串显示
将涂上背景色、再上面写字符、刷新这三部分写成一个函数:
x、y是显示位置的坐标,c是字符颜色,b是背景颜色,s是字符串,l是字符串的长度。
9、内容9:重新调整fifo缓冲区1
以前是每个定时器都配一个缓冲区,现在把他们都放在一个缓冲区里面,每次写入不同的数据来区分定时器。
在入口函数中:
10、内容10:测试性能
我们已经对程序不断的做了相应的改善,但如果想要知道程序究竟改善到了什么样的程度的话,还需要测试其性能。
先对HariMain进行一定的修改,恢复变量count,然后完全不显示计数,全力执行count++语句。当到了10秒后超时的时候,再显示这个count值。
在进入循环的时候添加count++,完全不显示计数,到了10秒后超时的时候再显示这个count值,在启动3秒的时候把count复位为0。这样的话计时时间就是7s。因为我们做的这个操作系统启动(初始化)的时候只要有些条件发生了变化,电脑初始化花费的时间就会发生很大变化,所以在3秒后再开始。
需要注意的是,在测试的10秒内,不要动鼠标或者按键,因为如果触碰到了鼠标或者按键的话,那么程序就需要进行光标的显示处理,这样会减缓count的增长。
11、内容11:重新调整fifo缓冲区2
如图,上次优化的时候我们将定时器的缓冲区设为共同的一个缓冲区,其实我们也可以将键盘、鼠标、定时器的缓冲区归纳为共同的一个。
其实除了代码的简化,这里也有一个逻辑上的细小的变化,以前是先把键盘终端的数据都读出来后然后处理鼠标的数据,最后再处理定时器的数据,假设键盘、鼠标一直源源不断地产生数据,是不是就可以理解为无法处理定时器的数据,这样会发生定时器“饥饿”,但是现在把他们都放到一个fifo里面,先进先出,可以尽量避免这种特殊情况的发生。
现在既然要使用一个共同的缓冲区,那么同样需要不同的数据来区分是来自什么中断。
这样出现的问题就是以前数据我们用的是char类型,它只有8位,它没办法表示那么多的数据,所以我们要把它改为int类型。
然后就要将所有的相关数据的类型都由char变为int类型,涉及到的函数有:
同样,鼠标和键盘的缓冲也要改为fifo32,数据类型改为int类型。
都修改完了后,就可以来修改入口函数:
修改结束后,再通过上面介绍的性能测试,可以发现我们的程序运行的更快了。因为在修改之前我们要多次查询各个缓冲区有没有数据,现在只需要查询一个缓冲区,因此使得“count++”在相同的时间内,执行的次数更多,程序运行得更快了。
12、内容12:加快中断处理
因为timer_settime虽然不是中断处理程序,但是依然是在中断禁止期间进行的,因此要快速的完成。现在使用的定时器比较少,只有3个,但如果任务增加,并且同时进行,需要更加多的定时器,并且还需要使用移位处理,这样便浪费了时间。(因为在之前的程序中,执行timer_settime函数时,就将定时器的timeout进行了排序,因为使用数组实现的,所以要插入到某一个位置时,就要将该位置以后的所以都向后移动一个位置)
因此,采用另一种方法,就是将使用的数据结构由数组换为链表(单向链表),加入一个next指针,其是一个地址变量,用来存放下一个即将超时的定时器的地址。这样在插入的时候就只需要改变两个指针的指向。
现在我们用一个链表将他们按照定时的大小从小到大将各个定时器串联了起来,因此就不需要timers[]了,但是我们需要一个变量来记录链表的头节点。
接下来要修改settime函数,我们的最终目的就是改良函数内的移位。
13、内容13:使用哨兵简化程序
这部分主要是简化settime函数,定时器在插入的时候,考虑了4种可能:
所以我们就认为的加入一个节点放在最后,定时的时间设置为0xfffffffff(永远都不会到达这个时刻),这样第一和第四种情况就永远不会发生,这种方法就叫加入哨兵(这种方法就和我们学数据结构的时候讲的哨兵一样)。
哨兵的加入在初始化PIT的时候:
如果使用需要调整的程序(一年调整一次)必须保证不改变哨兵时刻,哨兵的时刻调整:
然后简化timer_settime函数:
其实就是把那两种不可能发生的情况删掉了
简化inthandler20函数的最后一部分,因为不用再判断正在使用的定时器个数是否为0了,因为有了哨兵:
二、遇到的问题及解决方法
无
三、程序设计创新点
1、描述创新点1: 仿照windows页面,制作一个密码输入的页面。
运行效果:
四、实验心得体会
这两天的内容最关键的就是定时器的设置以及使用,然后大部分的篇幅都在介绍如何去优化它们。在优化的时候,真的很佩服作者的严谨性,书上的内容很详细,详细到我们可以直接了解到作者的思路的变化过程,这对我们自己思考问题的方式有很大的影响。还有就是感觉数据结构的作用很大,从数组到链表,还有数据结构的优化都会对我们的程序的性能起到很大的帮助。还有for循环、减少if语句,就是我们上学期在深入理解计算机系统中第五章的优化。