操作系统实验--30天自制操作系统第7天实验日志(第二次节点考核代码)

一、实验主要内容

1、内容1:获取按键编码
将中断处理程序进行改善,使其在按下一个键后不结束,而是将按键编码在画面上显示出来。
其主要是要修改int.c程序中的inthandler21函数,修改如下:
在这里插入图片描述

io_out8(PIC0_OCW2,0x61)表示PIC将会继续监视IRQ1中断是否发生。如果不执行这句话,PIC只会接收一次中断信号,就不再监视IRQ1中断,不管下次键盘输入了什么信息,系统都不会感知到。0x61则表示是1号中断,相应中断的表示为0x60+中断号
而编号为0x0060的端口输入的8位信息是按键编码,该设备就是键盘。
该段程序所完成的是将接收到的按键编码以16进制显示在画面上,然后结束中断处理。
实验结果(此时按下的键盘是A):
在这里插入图片描述

而且在按键是松开时也会触发一次中断,画面上也会显示一个按键编码

2、内容2:加快中断处理
中断处理程序,本质上就是打断CPU本来的工作,加塞要求进行处理,而且在处理进行的过程中,不能再接受别的中断。所以如果处理键盘的中断速度太慢,严重影响用户的体验。
而我们将字符显示的程序放在了,中断处理程序中。
而且字符显示属于I/O操作,需要大量的时间来处理,而且仅仅画一个字符时,就需要执行8x16=128次if语句,来判定是否要往VRAM中描画该像素。如果判定为描画该像素,还要执行内存写入指令。
所以我们可以先将按键的编码接收下来,保存到变量中,然后由HariMain偶尔去查看这个变量,如果发现有数据,就把它其显示出来。
Int.c中修改的部分:
在这里插入图片描述

上面的程序中,考虑到键盘输入时需要缓冲区来缓存数据,因此定义一个结构体,命名为keybuf,其中的flag变量用于表示整个缓冲区是否为空。如果flag为0,表示缓冲区为空,如果flag为1,就表示缓冲区中存有数据。当缓冲区中存有数据,如果再来一个中断,就暂时不对其做任何处理,且将这个数据扔掉。
Bootpack.c的HariMain函数:
在这里插入图片描述

一开始需要先用io_cli指令来屏蔽中断。
因为在执行之后的处理时,如果有中断进来(就可能改变结构体的数据),影响整个程序。这时候,先将中断屏蔽,然后来查看keybuf.flag的值。
如果flag的值为0,说明按键还没有被按下,keybuf.data里没有值保存进来。当没有值保存时,系统无事可做,就去执行io_hit。但由于已经执行io_cli屏蔽了中断,再去执行HLT指令的话,即使有按键被按下,程序也不会有任何的反应。
因此STI和HLT两个指令都需要去执行,而执行这两个指令的函数就是io_stihlt。在执行HLT指令后,如果收到了通知,CPU就会被唤醒,这样CPU会先去执行中断处理程序,执行完以后,又回到for语句的开头,再执行io_cli函数。
再接着,如果通过中断处理函数再keybuf.data中存入了按键编码,else语句就会被执行。先将整个按键编码值保存到变量i中,然后将flag置为0表示键码值清为空,最后再通过io_sti语句开放中断。
虽然在keybuf操作中有中断进来会造成混乱,但因为keybuf.data的值已经保存完毕,再开放中断也没有什么关系。
实验结果:
在这里插入图片描述

当按下右Ctrl键时,不管按下或者松开,屏幕上显示的都是E0,但是在第一个内容中,按下时显示1D,松开时显示9D。
因为当按下右Ctrl键时,虽然会产生两个字节的键码值E0 1D,松开时,会产生两个字节的键码值E0 9D。但在一次产生两个字节键码值的情况下,因为键盘内电路一次只能发送一个字节,所以一次按键就会产生两次中断,第一次中断时发送E0,第二次中断时发送1D。
在第一次的内容中,以上两个中断所发送的值都能收到,瞬间显示E0,接着又显示1D或者是9D,但在这里,HariMain函数在收到E0之前,又收到前一次按键所产生的1D或者9的D,而这个字节被舍弃了。
3、内容3:制作FIFO缓冲区。
内容2中由于作者所创建的缓冲区只能存储一个字节。如果能制作一个能存储多字节的缓冲区,就解决了问题。
最简单的解决方法是增加相关的变量,但这样便会使得程序变长,我们可以用数组来表示,但同时,我们在创建的缓冲中,我们所需要的是FIFO型。为什么?
因为当输入为ABC时,输出的时候也必须是ABC,因此需要按照输入数据的顺序输出数据,也就是先进先出的方式。
因此修改的int.c程序如下:
在这里插入图片描述

Keybuf.next的起始点是0,最初存储的数据是keybuf.data[0],下一个数据是keybuf.data[1],接着是[2],依此类推,一共有32个存储位置。
下一个存储位置用变量next来管理,这样便可以记住32个数据而不会溢出,next的值变为32后,多的数据就不要了。
取得数据的程序如下:
在这里插入图片描述

如果next不是0,就说明至少有一个数据,最开始的一个数据肯定是放在data[0]中的,将整个数据存入到变量i中,这样,数就减少了一个,所以将next减1。
通过for语句,数据的存放位置全部都向前移送了一个位置,如果不移送的话,下一次便不能从data[0]读入数据了。
实验结果(按下右ctrl):
在这里插入图片描述

但这样处理的结果仍然存在着问题,像这样移送数据的处理,一般来说也不超过3个。但在某些情况中,需要移送处理多达32个数据这样的时间开销就会很大,像这样的情况我们是需要避免的。
4、内容4:改善FIFO缓冲区
要改善上述内容的情况,就需要开发出一个不需要数据移送操作的FIFO型缓冲区。
基本思路:
不仅要维护下一个要写入数据的位置,还要维护下一个读出数据的位置。
但这样的缓冲区使用了一段时间后,下一个数据的写入位置便会变为31,而下一个数据的读出位置可能会变成29或30.当下一个写入位置变为32时,下一个地方便无法写入数据了。
如果当下一个数据写入位置到达缓冲区终点时,数据读出位置也恰好到达缓冲区的终点,也就是说缓冲区正好便空。这样只要将下一个写入位置和下一个数据读出位置都再置为0即可。
当下一个写入位置到达缓冲区最末尾时,缓冲区开头的部分已经变空了,如果还没有变空,就说明数据读出跟不上数据写入,只能将部分数据丢弃掉。因此如果下一个数据写入位置到了32以后,就强制性将其设置为0,这样下一个数据写入位置就跑到了下一个数据读出位置的后面。
对下一个数据读出位置也进行同样的处理,一旦到了32以后,将将其设置为从0开始继续读取数据。这样32字节的缓冲区便能一圈一圈地不断循环。虽然这个缓冲区只有32个字节,但是只要不溢出,其便能一直运行下去。
在这里插入图片描述

变量len是指缓冲区能记录多少字节的数据。
读出数据的程序如下:
在这里插入图片描述

实验结果:、
在这里插入图片描述

5、内容5:整理FIFO缓冲区
整理缓冲区,使其具有一定的通用性,能够在鼠标部分使用。当鼠标移动时,就会连续发送3个字节的数据。
定义一个结构体:
在这里插入图片描述

为什么要将缓冲区大小定义为可变的?
因为如果将缓冲区大小固定为32字节的话,以后改起来就不方便了。因此将其定义为可变的,缓冲区的总字节数保存在变量size里,变量free用于保存缓冲区里没有数据的字节数。变量buf保存缓冲区的地址。P代表下一个数据写入地址,q代表下一个u读出地址。
在这里插入图片描述

该函数是结构的初始化函数,用来设定各种初始值,也就是设定FIFO8结构的地址以及与结构有关的各种参数。
在这里插入图片描述

该函数是往FIFO缓冲区存储1字节信息的函数。Flag是用来记录缓冲区是否溢出。
Return语句后跟数字是什么意思?
当调用以上函数时,可以通过制定赋给i的值,来写出类似i=fifo8_put(fifo,data)。为了能够简单的确认到底有没有发生溢出,将其设为-1或0,分别表示有溢出和没有溢出这两种情况。
在这里插入图片描述

该函数是从FIFO缓冲区中取出1字节的函数。
在这里插入图片描述

该函数是用来调查缓冲区的状态。
使用以上函数写成如下的程序段:
在这里插入图片描述

在fifo8_put参数里,有一个&符号,在这里不是并符号,而是取地址运算符,用它可以取得结构或变量的地址值。Fifo8_put接收的第一个参数是内存地址,与之相匹配的是,这里调用的第一个参数也需要是内存地址。
实验结果:
在这里插入图片描述

6、内容6:总算讲到鼠标了
为什么之前的内容虽然连接着鼠标但是却不能用?
因为虽然之前在主板上做了鼠标用的电路,但只要不执行激活鼠标的指令,就不产生鼠标的中断信号。
所谓的不产生中断信号,也就是即使从鼠标传来了数据,CPU也不会接收。
因此需要制作发行指令,使得鼠标控制电路和鼠标本身其作用。鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,那么鼠标控制器的激活也就完成了。
键盘控制电路部分:
在这里插入图片描述

其中wait_KBC_sendready的作用是让键盘控制电路做好准备动作,等待控制指令的到来。
为什么要做这个工作?
因为虽然CPU的电路很快,但是键盘控制电路却没有那么快。如果CPU不管设备接收数据的能力,只是发送指令,那么有些指令就会得不到执行,从而导致错误的结果。如果键盘控制电路可以接收CPU指令,CPU从设备号码0x0064处所读取的数据的倒数第二位应该是0。在确定这位是0之前,程序一直通过for语句循环查询。
Break语句是从for循环中强制退出的语句,退出以后,只有return语句在哪里等待执行,因此,将这里的break语句换写成return语句,结果也是一样的。
其次,init_keyboard函数是一边确认可否往键盘控制电路传送信息,一般发送模式设定指令,指令中包含着要设定为何种模式,模式设定的指令是0x60,利用鼠标模式的模式号码是0x47。
在这里插入图片描述

该函数与init_keyboard函数非常相似,不同点仅在于写入的数据不同。如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标。另一方面,当鼠标收到激活指令以后,马上给CPU发送答复信息。这个答复信息就是0xfa。
在这里插入图片描述

7、内容7:从鼠标接收数据
鼠标和键盘的原理几乎相同,因此程序也十分的相似。
不同之处只有送给PIC的中断受理通知。IRQ-12是从哪个PIC的第4号,首先要通知IRQ-12受理已完成,然后再通知主PIC。这是因为主/从PIC的协调不能够自动完成,如果程序不教给主PIC该怎么做,那么它就会忽视从PIC的下一个中断请求。从PIC连接到主PIC的第2号上。
鼠标数据的取得方法与键盘完全相同,因为键盘控制电路中含有鼠标控制电路,所以才造成了这样的结果。中断号码是用来区分数据是来自键盘还是鼠标的关键。
因为鼠标往往会比键盘更快的送出大量数据,因此将其的FIFO缓冲区增加到128字节。在取得数据的程序中,如果键盘和鼠标的FIFO缓冲区都为空时,就执行HLT。如果不是两者都空,就先检查keyinfo,如果有数据,就取出一个显示出来。如果keyinfo为空,就去检查mouseinfo,如果有数据,就取出一个显示出来。
实验结果:
在这里插入图片描述

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

1、在进行键盘按键实验时按下按键A或者其他英文按键后再次按下其他按键无效1
导致该问题的原因与设备有一定关系,有些电脑没有这个问题,如果有可以先锁定大写,然后按键可以正常触发中断。
2、节点考核中第3题画出一个圆,但当圆形移动到右边界和下边界时会有一个点消失然后出现在最左边和最上边
导致该问题的原因是由于画的图形越界了,而显存地址是连续循环的,所以越界的数据存到开头的部分去了,这是越界出现小点的原因。
哪为何我们写的画圆函数会出现这种情况呢?
这是因为我们画出来的圆因为在这些像素点的情况下只是近似一个圆的,这个圆本质上还是由一个个像素点组成。而我们确定一个圆通常由圆心坐标和半径组成,我们写的相关画圆函数也是需要这几个参数,而问题就出现在这,我们定义的半径为10的圆画出来真的是直径为20的圆吗?
结果不是的,因为我么还有一个圆心占了一个像素点,所以我们画出来的圆实际上直径是2*R+1,所以我们的圆直径通常和我们的屏幕像素数不能整除,而我们却以为可以整除,在最左边和最上边的时候,因为像素点编号是从0开始的,所以将圆心限制在不能小于半径的地方是可以的,因为此时实际上的距离是R+1,但到了最右边和下边的时候我们设置通常是在320-R和160-R处的限制,而圆在这些地方时,因为圆心占了一个像素,所以圆心到边界的距离会比半径小一个像素点,这就是我们出现越界的原因了。
那么如何改正呢?
因为这是像素点拼凑出来的圆所以,想达到直径为偶数是不可能的,那么我们的解决方向就在于在图像向边界移动最后一次时的距离,我们一般移动的距离如果为step的话,正常情况下,圆会有一个点越界,但我们在最后一步时移动step-1个像素点,这样就可以解决了,同理图形在边界离开时也只能移动step-1个像素点。具体解决代码见下面创新点。

三、程序设计创新点

1、在屏幕上显示一个实心圆,键盘上的上、下、左、右四个箭头或任意4个相邻的字母分别控制圆形移动的方向,按下某个按键,圆形向其对应的方向移动。圆形的初始位置在屏幕中央,移动到达屏幕边界后如果继续按边界方向对应的按键则不能再移动,此时在屏幕上打印已到达边界的提示,例如“It has reached the boundary.”,同时还具有加快减慢图形移动速度和改变图形颜色功能
要完成这个功能首先我们需要一个画圆的函数
这里先是一个先相应坐标像素点存入颜色的函数,
画圆,简单粗暴遍历所有的像素点,满足圆的方程的就画上颜色
在这里插入图片描述

然后我们要利用键盘中断传来的按键编码,在今天的内容中作者已经写好了中断的信息接收函数,这里只要我们根据按键编码所对应的值就能知道是哪个按键按下了
在这里插入图片描述

按键与按键编码的部分对应如图,如果想知道其他的
按键编码,可以在前面几步的实验中进行测试得出。
在这里插入图片描述

得到按下的按键信息后就该是我们的处理了,首先在处理之前我们先将原来的圆清除掉
在这里插入图片描述

这里因为我的操作按键是数字,所以这里对数字的编码进行了翻译,现在n的值就是按下的数字。因为我的背景色是0号颜色黑色,所以这里用黑色的圆覆盖原来的圆。
接下来就是我们对每一个按键的处理:
在这里插入图片描述

这里以向左移动为例,flag变量
是用来消除连续中断响应的,因为根据前面的实验可以发现,每一个按键在按下和抬起时都会触发中断并发送按键编码,虽然按下和抬起的编码不一样但这里任会响应两次,所以这里我们用一个变量flag来只响应一次中断处理(每次处理过后flag进行取反操作,这样在只有flag=0时才进行处理,就可以达到两次响应只处理一次了)

前面两个功能先放着我们来看看移动是如何实现的,第一步判断x是否等于299,是因为我在图形移动时我设定的圆心所能够到达的最右边是320-20-1(20是我的圆半径,如果采用其他半径,这里就应该是320-R-1的临界),这是为了保证圆图形不会越界,越界原因见上面遇到的问题,所以如果圆心此时在最右边边界,那么左移时也同样需要减少1点像素点,下面判断的是图形在最左边的临界条件(临界应该为R=20),如果此时圆在左边临界出,而此时操作又是向左移动,那么我们要显示一个提醒,同时告诉系统,现在有提醒显示了(即flag2=1,便于后面清楚掉),最后因为我们的步长是有可能会超过我们的半径的,所以在这x_-step小于我们的左临界值R的时候,直接进行移位到左临界值R=20处 ,最后除以上情况外,图形正常移动
X_坐标直接减step即可。
然后是右移
也是看移动部分第一步判读是否在右临界值(320-R-1=299),如果是,而此时又是右移,那么应该显示提醒,后续标志位flag2作用同上,后面判断的x==320-R-step=300-step表示的是此时再先右移就会到右临界值(320-R-1)了,但此时如果直接移动step的步长就会发生越界,所以此时移动step-1的步长,最后在步长大于半径时判断移动时会不会越界,如果越界,就直接移位到右临界值,然后,除上述条件之外,正常移位。
在这里插入图片描述

上移和左移类似,下移和右移类似,只是将320改为200,X_改为y_
除移动的4个按键外,还有以下4个(减慢类似于加快就没贴出来)功能,这些功能都是简单的修改标志位或属性即可,因为我们程序的最后一步会重新画出圆来,所以这些改变都可以生效。
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

最后我们来说一下闪动的实现和清楚文字部分:
在这里插入图片描述

颜色闪动:在闪动选项开启时每次触发移动按键时将颜色进行改变
清楚文字:在提醒发出后,只有我们不再继续往边界移动那么,提示就应该消失,flag2==1表示有提示出现了,这里无条件将其清除(因为后面有提示出现的语句,所以如果条件符合,提示人会出现)
如果想要实现圆大小改变功能,将上述公式中20改为变量R,然后添加一个改变R的操作指令,注意一下越界情况就可以了,全部代码(由于比较长,放在最后面了)
2,在屏幕上画一个彩色的“田”字,按下键盘退格键“Backspace”,“田”字消失。
每一笔划的颜色不同,相交的地方选相交的两笔划颜色之一即可。
实现步骤有两点:
1 画出彩色的田:使用作者的函数boxfill8即可
在这里插入图片描述

然后在中断处进行判断将田覆盖即可(backspace按键编码为0x0e)
在这里插入图片描述

3,在屏幕上显示自己的中文名字和学号(少数名族的同学可用简称),按下键盘某个指定键(自己设置)后名字和学号的颜色开始随时间动态变化。
要求:颜色不少于5种,变化速度不能过快,人眼要能很好的分辨出颜色的变化。
实现的关键在于两点:

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值