30天自制操作系统——第8天实验总结

实验日期 实验项目
2020.11.19 第8天 鼠标控制与32位模式切换

一、实验主要内容

1、 内容1 鼠标数据的解读

(1).内容概要

  • 实验内容:对接收到的鼠标数据进行处理。
  • 实验重点:对处理鼠标数据的关键操作的理解,如范围的判断;对鼠标数据表示含义的理解。

在第7天,可以将鼠标中断产生的数据收集起来,并存储在对应的缓冲区中。本次实验进一步对数据进行解读。鼠标的数据信息一般是3个字节为一组的,在开始接收数据时,当收到0xfa的答复时,buf数组存储数据,buf积累了3个字节的数据后,就将数据显示在屏幕上,然后再重新开始接收数据。

鼠标接收的数据信息含义如下表所示
在这里插入图片描述
(2).关键代码分析
Harib05a中的代码
鼠标数据接收代码处理

在这里插入图片描述
这部分代码就是每次中断接收3个字节数据的过程,只有前一个字节的数据存储好后,才会开始下一个字节数据的收集。每次中断只能收集到一个数据。

Harib05b中的代码
Harib05b只是对原来的代码进行了整理,将接收3个字节数据的功能封装为一个函数mouse_decode,大大增强了代码的可读性,利用函数的返回值判断是否接收到3个数据,只有当返回值为1时,才执行显示数据的操作。

在这里插入图片描述
Harib05c中的代码
Harib05c中增加了对鼠标坐标的处理,并实现将坐标显示到屏幕上。另外单击鼠标左右键时,也会有对应l和r字母变成大写的响应。

在这里插入图片描述
这里是对数据合法性的检测。0xc8转化为二进制是1100 1000,dat的高4位和0xc8相与,只有当这4位变化为0000,0001,0010,0011的时候,相与结果才能是0,以此来限制高4位的取值不能超过3;低4位与0xc8相与,只有当第4位为1时,才能保证相与结果为8,其他位和0相与,可以任意取值,以此来限制低4位的取值至少为8,最大取到F。

在这里插入图片描述
这部分代码是获取鼠标移动的坐标信息和状态信息。鼠标状态存储在第一个字节的低3位,构造一个低3位为1,其他位为0的数据,即0x07,执行与操作就可以将状态信息分离出来;坐标变化量的信息存储在第二三个字节,当鼠标中心点向左移动时,变化量应该为负值,故使用if判断,将mdec->x用补码形式表示,这样在屏幕上显示的就是负数了,鼠标中心点向上移动时,处理类似。

在这里插入图片描述
这部分代码是鼠标状态信息和左右中对应起来,001,010,100分别对应单击鼠标左键,单击鼠标右键,单击滚轮,更换对应的字符即可。

2、 内容2 鼠标的移动

(1).内容概要

  • 实验内容:修改图形显示部分,使鼠标移动起来。

(2).关键代码分析

在这里插入图片描述
这部分代码是实现鼠标移动的关键代码。实现思路:每次绘制鼠标之前,首先将上一次移动鼠标隐藏(以免造成叠影),接着改变鼠标的坐标,即坐标加上之前收集到的鼠标坐标变化量的值,这里需要对是否越界进行判断处理,最后绘制鼠标即可实现移动功能。这样实现的鼠标存在一定的bug,当移动到下端时,会将原图像擦除,这个问题在创新点中进行了解决。

3、 内容3 通往32位模式之路

(1).内容概要

  • 实验内容:asmhead.nas中的代码分析
  • 实验重点:理解进行32位模式之前需要做的准备工作,如打开“A20GATE”、切换到“保护模式,装载数据。

(2).关键代码分析
下面对asmhead.nas中的代码一一进行分析

  • 中断屏蔽处理

首先关闭主从PIC的中断,接着关闭CPU的中断,在CPU切换模式的时候是不允许有任何中断进来的,以免造成混乱。

在这里插入图片描述

  • 打开A20GATE开关,使内存1M以上的空间可用

A20GATE是后来CPU地址线发展到32位后,为了兼容旧版的操作系统,在执行激活指令之前,电路被限制只能使用1MB的内存,这根信号线有效时内存就可以使用1MB以上的内存空间了。代码中通过给键盘控制电路发送指令,使键盘控制电路附属端口输出0xdf,完成将A20GATE 信号线变成ON状态的功能。

在这里插入图片描述
waitkbout函数的说明:从0x64端口中读数据到AL中,和0x02相与,如果结果不为0就跳转到waitkbout继续等待。

在这里插入图片描述

  • 切换到保护模式

第一句中的INSTRSET指令是为了能够使用386以后的LGDT,EAX ,CR0等关键词。LGDT是将一个临时GDT读取进来(以后还需要重新设定,这是只是为了切换到32位模式而做的准备)。

接下来将CR0寄存器中的值赋值给EAX,进行相关模式的设定——最高位设0,禁用颁,最低位设1,为了能够切换到保护模式。

最后使用JMP跳转到pipelineflush,修改段寄存器中的值。进入保护模式以后,段寄存器的意思也变了(不再是×16),这里解释为能够使用GDT;除了CS(CS变了会造成混乱)以外所有段寄存器的值都从0x0000变成了0x0008(相当于GDT+1的段)。

在这里插入图片描述

  • bootpack的拷贝

这部分是使用memcpy函数将bootpack.c函数,引导扇区和10个柱面的内容全部转移到32位模式下专门为其预留的空间中去。

memcpy函数是复制内存的函数,使用格式为memcpy(转送源地址,转送目的地址,转送的数据大小),根据目的地址和转送的数据大小就可以确定出要搬运的具体位置。

转送数据的程序可以分为以下三个部分,第1部分是对bootpack的转送,转送512kb刚好是bootpack.hrb的大小。第2部分是对启动区的转送,从0x7c00转送512byte到0x00100000,。第3部分是对软盘剩下的所有柱面的转送,从0x00008200转送cyls51218*2-512byte到0x00100200,复制字节数中18表示18个柱面,2表示磁头数,减去的部分是一开始启动区的那部分。由于每次转送数据是以双字为单位的,故转送数据大小处需要除以4。和该汇编等价的C语言形式如下所示:

在这里插入图片描述

  • bootpack的启动

这段代码是继续进行数据的转送,对bootpack.hrb的头部进行解析,将执行需要必要的数据转送过去。EBX代入的是BOTPAK ,取出bootpack.hrb后的第16号地址中的值存入ECX,ADD使ECX加3,SHR使ECX除以4,最后用JZ判断ECX中是否为0。当ECX为0时,没有要转送的数据,就跳转到skip,否则就将bootpack.hrb中从0x10c8开始的0x11a8个字节的数据转送到0x310000中。最后将0x310000代人到ESP里,然后用一个特别的JMP指令,将2*8代入到CS里,同时移动到0x1b号地址。这里的0x1b号地址是指第2个段的0x1b号地址。

在这里插入图片描述

  • 往GDT0和GDTR0中传送数据

ALIGNB 16表示一直添加DB 0,直到地址能被16整除。
GDT0是一种特定的GDT,0号是空区域,不能进行定义,这里直接填充8个字节的数据0,接下来是将GDT+1和GDT+2两个段的相关设定用DW指令压入文件中。GDTR0是通知GDT0,GDT已经初始化好了。

在这里插入图片描述

  • 恢复中断

当做完所有在asmhead.nas函数中的工作后,在主函数中重新设定GDT,IDT,并启动中断。
最后笔者对内存分配进行了总结,如下表所示

地址 用途
0x00000000-0x000fffff 在启动时使用(1MB)
0x00100000-0x00267fff 用于保存软盘的内容(1440KB)
0x00268000-0x0026f7ff 空(30KB)
0x0026f800-0x0026ffff IDT(2KB)
0x00270000-0x0027ffff GDT (64KB)
0x00280000-0x002fffff bootpack.hrb(512KB)
0x00300000-0x003fffff 栈及其他(1MB)
0x00400000-

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

1、 描述问题1

  • 问题描述

将坐标转化为带有符号的判断为何是向左移动是和0x10相与,向上是和0x20相与?
在这里插入图片描述

  • 解决方法

结合之前测试得到的鼠标第一个字节数据的高4位表示移动的方向,当我们向左移动时,变化量为负,对应的区域时左上和左下,即编码应该为01和11,只有为01/11时才能保证相与结果不为0,说明此时该设置成负数了;同理可以解释向上移动是和0x20相与来进行判断。

2、 描述问题2

  • 问题描述

书上关于CR0寄存器的相关介绍很少,只有涉及到最高位和最低位的简单解释,为什么这样设定后就能顺利切换到我们所需要的保护模式呢?

  • 解决方法、

查阅资料可以知道,CR0的第0位是PE,即protection enable,第31位是PG,即paging。如果PE=0、PG=0,处理器工作在实地址模式下;如果PG=0、PE=1,处理器工作在没有开启分页机制的保护模式下;如果PG=1、PE=0,此时由于不在保护模式下不能启用分页机制,因此处理器会产生一个一般保护异常,即这种标志组合无效;如果PG=1、PE=1,则处理器工作在开启了分页机制的保护模式下。而我们实验中不需要分页机制,故是切换到无分页机制的保护模式下,则PE设为1,PG设为0。

3、 描述问题3

  • 问题描述

waitkbout函数中的读取设备0x64中的数据为啥要和0x02相与,以此来判断是否继续等待?

  • 解决方法

查阅资料知道,读取设备0x64的数据就是读取了一个状态寄存器中的内容,从右往左第2位位1时,说明缓存器是满的,要向0x64设备写入,必须等到缓冲器为空时才可以,故需要和0x02相与,判断这一位是否为0。

4、 描述问题4

  • 问题描述

在bootpack启动的那部分汇编代码中,为啥要对ECX进行加3操作?

  • 解决方法

根据后面JZ指令是对ECX里面的值进行判断,而ECX中保存的是需要转送的数据大小。当ECX为0时,不进行数据转送,ECX小于4时,如果不进行加3操作,ECX/4的值依然为0,此时有数据但没进行转送,故需要加上3来消除这种影响。

三、程序设计创新点

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

  • 创新点1

在之前移动鼠标时会出现擦除原背景图像的错误,这里提供一种比较简单的思路。既然会将原背景图像擦除,那在绘制鼠标后,再次绘制一遍背景就可以了。

  • 关键代码

在这里插入图片描述

在隐藏鼠标之前,重新绘制一遍背景颜色,调用函数init_screen8()。

  • 结果截图

下图已经基本解决了擦除原图像的问题,美中不足的是还有两个小三角形的颜色一直是背景色。

在这里插入图片描述

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

  • 创新点2

修改创新点1存在的问题,将鼠标对应矩形中的图像信息在绘制下一个鼠标之前保存起来,将保存起来的图像信息用来初始化鼠标的数组,即鼠标矩形的背景颜色用保存的图像信息替代。代码框架:按照保存起来的信息绘制图形,相当于之前隐藏鼠标的作用,只不过这里隐藏用的是该位置原图像的信息,计算得到下一位置处的鼠标坐标后将鼠标矩形信息保存起来,更新鼠标数组mursor(背景色用原位置处的图像信息表示,这里就是对创新点1的改进),最后绘制鼠标。

  • 关键代码

记录鼠标的像素信息的代码
在这里插入图片描述
这里使用了在中断外定义的一个256大小的char型数组,将从(x0,y0)开始的16*16的矩形图像信息保存起来,存储到一个临时数组temp。

修改鼠标矩形的背景颜色
在这里插入图片描述
鼠标矩形背景颜色处直接用temp中的图像数据替换。
在这里插入图片描述
进入中断之前,对temp数组进行初始化,这里是防止第一次中断temp中没有数据时,在屏幕上出现一个黑色16*16的矩形。
在这里插入图片描述
计算鼠标下一位置坐标之前,先按照temp中的数据绘制一遍(隐藏鼠标)
在这里插入图片描述
计算下(mx,my)开始位置的图像信息,并以此更新mcursor数组,最后绘制矩形即可,这些功能的实现均是调用已经封装好的功能函数。

  • 结果截图
    在这里插入图片描述

四、实验心得体会

本次实验是自制操作系统的第8天,这一次实验的主要内容是继续对鼠标数据进行处理,使得鼠标最终能够移动起来,另外讲解了之前用到的asmhead.nas文件中关于进入32位模式的相关工作的准备。通过这一次实验,体会到一个操作系统真正核心的东西是需要基于汇编指令编写的,使用汇编才能更好对该系统的相关属性进行设置,例如这次asmhead.nas中的中断屏蔽,切换到保护模式,装载bootpack.hrb,建立临时GDT等工作,而高级语言只是对系统进一步实现,或者是增加软件功能。实验中涉及到内存的内容也是比较多,下一天的内容刚好就是内存管理,相信学习了第9天的内容,再结合操作系统原理理论课程的内存管理的相关内容,会对这门课程制作的操作系统更加了解。

猜你喜欢

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