30天自制操作系统——第12-13天实验总结

实验日期 实验项目
2020.12.17 第12,13天 定时器

一、实验主要内容

1、 内容1 使用定时器

(1).内容概要

  • 实验内容:设定定时器,并能够使用定时器来计量时间。
  • 实验重点:定时器实现的基本原理

定时器(Timer)对于操作系统非常重要,其工作原理是每隔一段时间就发送一个中断信号给CPU,让CPU知道当前的时刻。定时器具有如下作用:

	实现每隔一段时间/某个时刻进行某种处理(如让钟表的秒针动起来)。
	在HLT的时候可以知道时间(根据定时器中断处理程序发生的次数)。

管理定时器的时候需要对PIT(可编程的间隔型定时器)进行设定,通过设定PIT,可以让定时器每隔一个固定时间发生一次中断,在硬件上,PIT连接着IRQ0,所以每隔一段时间都会通过IRQ0告知CPU需要发生中断。下面本次实验中设计PIT的相关硬件知识整理如下

IRQ0的中断周期变更:AL=0x34;OUT(0x43,AL)  AL=中断周期的低8;OUT(0x40,AL) 
                  AL=中断周期的高8位;OUT(0x40,AL)

如果指定中断周期为0,会被看作是指定为65536。实际中断产生的频率是单位时间内的时钟周期数(即主频)/设定的数值。比如设定值为100,则中断产生的频率就是1.19318KHz。本次实验中设置设定值为11932,中断产生的频率是100Hz,即每10ms发生一次中断。

(2).关键代码分析

对PIT进行初始化。HariMain函数中调用init_pit函数对初始化。
在这里插入图片描述
中断处理程序与键盘中断处理程序基本一致。这里不做详细说明。需要注意的是必须在naskfunc.nas中注册PIT的中断处理程序,在init_gdtidt函数中加上IDT的设定。
在这里插入图片描述在这里插入图片描述

2、 内容2 计量时间

(1).内容概要

  • 实验内容:使用定时器来实现计量时间的功能

每次定时器发生中断时,计算变量就以1递增,每1秒钟就会增加100。利用这个方法可以知道从启动开始时间过去了多少秒。

(2).关键代码分析

扫描二维码关注公众号,回复: 12682993 查看本文章

程序首先定义了一个结构体。在结构体中添加一个count作为计数变量。初始化PIT时将其设置为0。每次发生中断时,count值增加1。
在这里插入图片描述
在HariMain函数中将count的值显示出来。
在这里插入图片描述

3、 内容3 超时功能

(1).内容概要

  • 实验内容: 实现超时功能,即每隔一个时间(时间可编程设置),操作系统执行某个操作。

(2).关键代码分析

  • TIMERCTL结构体的实现

在这里插入图片描述
在init_pit函数中加上对timeout的初值设置。在中断处理函数中实现超时功能。实现思路:每次中断判断timeout是否为0,不为0则timeout减少1,为0则使用fifo8_put函数向fifio缓冲区中发送数据。

设置settimer函数,目的是设定超时时刻。实现思路:取出eflags中各标志位的值,使用io_cli()函数禁止中断,防止其他中断进来引起混乱。完成设定后,恢复eflags中各标志位的值,恢复中断。
在这里插入图片描述
HariMain函数中编写测试代码来验证功能实现的情况。程序设定10s后从timerfifo写入1这个数据,当其接收到数据时,就会在屏幕上显示“10[sec]”。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

(1).内容概要

  • 实验内容: 使用多个定时器,通过超时来实现不同的功能,如鼠标光标的闪动,电子时钟每隔1秒重新显示一次。
  • 实验重点:了解超时的原理,能够根据多个定时器的超时实现一些动画效果。

(2).关键代码分析

增加一个结构体TIMER,专门用来设置每个定时器的相关属性,而原来的TIMERCTL则作为MAX_TIMER 个定时器的管理结构。
在这里插入图片描述
定义两个宏,表示定时器的状态,1表示已经配置好,可以随时使用;2表示定时器运行中,0表示可以使用但是尚未配置好。
在这里插入图片描述
在init_pit函数中利用for循环将每个定时器的flags均设为0。timer_free函数用来释放定时器;timer_init函数用来设置缓冲区及其数据;timer_settime设定超时时刻,并将该定时器的标志位设置为正在使用。

timer_alloc函数用来找到一个空闲(没有使用)的定时器分配出去。实现思路:for循环遍历定时器,通过flags的值来判断是否使用,找到后将其返回,停止循环。
在这里插入图片描述
中断处理函数inthandler20每次中断将计数变量增加1。处理思路是利用for循环找到正在使用的定时器,超时时刻减少1,检测此时是否到达超时时刻,如果到达超时时刻则将其标志为已经配置好,并向缓冲区中发送数据。
在这里插入图片描述

HariMain函数中使用定时器的基本步骤,以代码为例子进行说明。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、 内容5 加快中断处理1,2,3

(1).内容概要

  • 实验内容: 优化程序,加快中断处理

修改1:从读写内存角度减少在中断处理时所用的时间,采用的方法是减少对内存数据的读写。CPU完成从内存中读取变量值,减去1,然后就又需要往内存中写入,两次访问内存需要花费较多的时间。局限:count值最大只能取0xffffffff,即约497天,这样的话需要每隔497天重新启动一次操作系统。

修改2:从代码角度减少程序的执行次数,采用的方法是添加next变量,用来记录下一个超时时刻。

修改3:参考图层中*sheets[]的实现,将所有定时器按照超时时刻进行排序

(2).关键代码分析

  • 修改1的关键代码

inthandler20中断处理函数中timeout用来表示超时发生的时刻(予以时刻),而count表示现在的时刻,所以可以通过两者的大小关系来判断是否超时(timeout小于等于count 则说明发生超时)
在这里插入图片描述
timer_settime函数中需要在传入参数timeout的基础加上count值作为给予的超时时刻。

  • 修改2中的关键代码

在TIMERCTL中添加一个next变量,用来记住下一个超时时刻。
在这里插入图片描述
在inthandler20函数中,大多数情况下,第一个if语句的return都会执行,中断处理就到此结束了。当到达下一个时刻时,使用之前那种方法检查是否超时。超时的话,就写入到FIFO中;还没超时的话就调查是否将其设定为下一个时刻(未超时时刻中,最小的时刻是下一个时刻)。使用这样的方法就能大大减少if语句的执行次数。
在这里插入图片描述
在init_pit函数中需要增加对next变量的设置,设置初值为0xffffffff,表示最初没有正在运行的定时器。timer_settime函数中需要对下一个超时时刻和设定的超时时刻进行比较,用来更新下一次的时刻。代码如下:
在这里插入图片描述

  • 修改3中的关键代码

在TIMERCTL中添加*timer作为排序好的定时器,using记录现有的定时器有多少个处于活动中。

inthandler20处理函数实现思路:遍历所有处于活动中的定时器,如果找到第i个定时器没有发生超时就退出循环,由于timers是按照超时时刻的顺序排列的,所以正好有i个定时器超时了,将using更新为using-i,将其余的定时器进行移位。如果此时还有处于活动状态的定时器,则下一个超时时刻就是timers[0]的超时时刻。

在这里插入图片描述
由于timerctl中的变量名改变了,对于init_pit函数和timer_alloc函数中对应的地方也需要进行修改。对于timer_settime函数,必须将timer注册到timers中去,并且需要按照顺序将其注册好。为了避免注册时中断的干扰,需要事先使用io_cli()函数关闭中断。代码实现如下:
在这里插入图片描述

6、 内容6 简化字符串显示

(1).内容概要

  • 实验内容: 将程序中反复出现的字符串显示代码段封装成一个函数模块。

(2).关键代码分析

putfonts8_asc_sht函数将“涂上背景颜色,写上打印字符,完成刷新”三个步骤封装起来,可以直接调用,简化代码量。其中sht是操作的图层,x,y是显示位置的坐标,c是字符颜色,b是背景颜色,s是字符串,l是字符串长度。
在这里插入图片描述

7、 内容7 调整fifo缓冲区1

(1).内容概要

  • 实验内容: 将定时器使用多个FIFO 缓冲区合并成为一个。

如何辨别不同的定时器:通过向定时器缓冲区写入不同的数据来分辨哪个定时器超时。

(2).关键代码分析

使用timerfifo表示定时器缓冲区,向缓冲区写入数据10,3,1分别表示定时器timer,timer2,timer3。
在这里插入图片描述
HariMain函数中对应部分也做出相应的修改。首先是简化了定时器缓冲区是否为空的判断,其次用缓冲区中特定数据判断哪个定时器,更加方便。
在这里插入图片描述

8、 内容8 测试性能

(1).内容概要

  • 实验内容: 对改善前后的定时器程序进行测试。
  • 测试方法:使用count值进行计数,当达到10s超时时,将count的值显示出来。需要注意的是在启动3s后要将count值复位为0。

3s后将count值复位的原因:当某些条件发生轻微变化时,电脑初始化所花费的时间就会产生很大的变化。3s后进行计数可以很好地减少误差。

(2).关键代码分析
在这里插入图片描述
笔者在QEMU模拟器上和真机上分别进行了测试,由于QEM U可能受到windows的影响数据结果比较发散,不便于比较结果。在真机上的测试结果如下
在这里插入图片描述
可以看到每次改良程序的性能都能有所提高。

9、 内容9 调整fifo缓冲区2

(1).内容概要

  • 实验内容: 将定时器缓冲区,鼠标缓冲区和键盘缓冲区合并成为一个。

使用不同的数据来区分定时器,鼠标和键盘
在这里插入图片描述
为了使用像767这样比较大的数字,需要将原来fifo8结构中的char修改为int型,这里记作fifo32,每一处使用的fifo8缓冲区的地方都要进行相应的修改。

(2).关键代码分析

键盘相关代码的修改。init_keyboard函数中需要增加对fifo缓冲区的信息保存,inthandler21中断处理函数中将data发送fifo缓冲区,需要注意的是要将data加上256,即keydata0。
在这里插入图片描述在这里插入图片描述
鼠标相关代码的修改。enable_mouse函数中需要增加对fifo缓冲区的信息保存,inthandler2c中断处理函数中将data发送fifo缓冲区,需要注意的是要将data加上512,即mousedata0。
在这里插入图片描述在这里插入图片描述
HariMain函数的相应修改
在这里插入图片描述

性能分析
在这里插入图片描述在这里插入图片描述
运行两个程序,前后对比发现性能确实有了很大的提升。分析原因:count++和查询两个操作是交互进行的,合并缓冲区后,fifo缓冲区查询能够更快完成,count++执行的次数自然会得到增加。

10、 内容10 加快中断处理4

(1).内容概要

  • 实验内容: 消除第12天中定时器的移位操作。

使用next变量记录下一个超时定时器的地址,这样就可以直接找到下一个超时时刻,避免了繁杂的移位操作。类似于链表的结构,连接关系如下:
在这里插入图片描述
(2).关键代码分析

inthandler20函数中修改的内容不多,由于是采用链表的结构,找到最先达到超时时刻的定时器后可以直接赋值给t0(链表头节点),相当于之前的timers[0]。
在这里插入图片描述
timer_settime函数中采用链表结构实现的关键部分。这部分代码需要将新设定超时的定时器加入到合适的位置,插入情况可以分为3类,下图说明在中间插入的情况。找到待插入位置后(s,t之间),将s的next指向timer,timer的next指向t。
在这里插入图片描述
代码实现如下:
在这里插入图片描述
在这里插入图片描述
需要注意的是TIMERCTL中的next和TIMER中next表示的含义不同,前者的next表示下一个超时时刻,后者表示下一个定时器的地址

11、 内容11 使用“哨兵”简化程序

(1).内容概要

  • 实验内容: :使用哨兵简化程序。

在hari10b10h中timer_settime函数中总共有4种可能的情况:

	运行中的定时器只有一个
	插入到最前面的情况
	插入到s和t之间的情况
	插入到最后面的情况

这里使用哨兵来减少可能发生的情况。具体的做法是:在进行初始化时,将时刻0xffffffff的定时器连接到最后一个定时器上,这个定时器就是所谓的哨兵。这样修改后第1,4种情况就不可能发生了(有哨兵,定时器最少有2个;哨兵在最后,不可能插入到最后),因此在timer_settime函数种只需要考虑两种插入情况即可。

(2).关键代码分析

timer_settime函数中去掉第1,4种情况的代码即可。inthandler20函数中可以去掉using,这里的using已经没有任何作用了。
在这里插入图片描述

二、遇到的问题及解决方法

1、 描述问题1

  • 问题描述

P256页的识别键盘缓冲区的数据为什么要加上256?

  • 解决方法

和定时器的标记区分开,这里只错开了256位,如果后续设计中不够使用可以进行进一步扩充,鼠标和键盘的键值加上的值笔者用一个变量代替,可能也是考虑到这种情况。

三、程序设计创新点

设计了操作系统的开机动画。开机动画为一滴水从高处落下,泛起阵阵涟漪,消失在窗口外,接着四个矩形从四个角落向中间位置汇聚,出现图像加载画面,加载结束后进行登陆画面。

1、 描述创新点1,关键代码及结果截图

  • 创新点1

水滴滴落造成涟漪效果的动画。实现思路:将数据2-19,20-43定义为定时器timer的存储数据,每一个数字对应一帧动画,在init_start函数中通过flag标志位来判断需要绘制哪帧动画,定时器时间设置为0.1s。

  • 关键代码

在这里插入图片描述
在这里插入图片描述

  • 结果截图

在这里插入图片描述在这里插入图片描述

2、 描述创新点2,关键代码及结果截图

  • 创新点2

四个矩形从窗口的4个角落向中间汇聚,汇拢后出现加载圆圈。为了简化bootpack.c函数中的代码量,将init_start等和开机动画相关的函数单独写在start.c文件中,便于管理和使用。动画实现思路和水滴滴落的类似。

  • 关键代码

在这里插入图片描述

  • 结果截图

在这里插入图片描述
在这里插入图片描述
最后显示登陆界面(这个界面是暂时的,后续还会修改,有点小丑)
在这里插入图片描述

四、实验心得体会

本次实验是自制操作系统的第12和13天,这两天的内容主要是对定时器的使用。重难点在于笔者对程序的不断优化,在这个过程中也体会到了设计开发操作系统时需要对某个细节不断思考,斟酌,做出尽可能多的优化,哪怕是一点点改善也许对提升性能都会有很大的帮助。这两天的另外一个收获就是可以使用定时器制作动画了,以前是利用循环制作动画,对于时间的掌控效果不是很好,学习了定时器后,可以对动画的时间实现很好的控制,达到更加丝滑,完美的实现效果。

猜你喜欢

转载自blog.csdn.net/weixin_44595362/article/details/114270250