实验日期 | 实验项目 |
---|---|
2020.11.19 | 第8天 鼠标控制与32位模式切换 |
(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分别对应单击鼠标左键,单击鼠标右键,单击滚轮,更换对应的字符即可。
(1).内容概要
(2).关键代码分析
这部分代码是实现鼠标移动的关键代码。实现思路:每次绘制鼠标之前,首先将上一次移动鼠标隐藏(以免造成叠影),接着改变鼠标的坐标,即坐标加上之前收集到的鼠标坐标变化量的值,这里需要对是否越界进行判断处理,最后绘制鼠标即可实现移动功能。这样实现的鼠标存在一定的bug,当移动到下端时,会将原图像擦除,这个问题在创新点中进行了解决。
(1).内容概要
(2).关键代码分析
下面对asmhead.nas中的代码一一进行分析
首先关闭主从PIC的中断,接着关闭CPU的中断,在CPU切换模式的时候是不允许有任何中断进来的,以免造成混乱。
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的段)。
这部分是使用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.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号地址。
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- | 空 |
将坐标转化为带有符号的判断为何是向左移动是和0x10相与,向上是和0x20相与?
结合之前测试得到的鼠标第一个字节数据的高4位表示移动的方向,当我们向左移动时,变化量为负,对应的区域时左上和左下,即编码应该为01和11,只有为01/11时才能保证相与结果不为0,说明此时该设置成负数了;同理可以解释向上移动是和0x20相与来进行判断。
书上关于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。
waitkbout函数中的读取设备0x64中的数据为啥要和0x02相与,以此来判断是否继续等待?
查阅资料知道,读取设备0x64的数据就是读取了一个状态寄存器中的内容,从右往左第2位位1时,说明缓存器是满的,要向0x64设备写入,必须等到缓冲器为空时才可以,故需要和0x02相与,判断这一位是否为0。
在bootpack启动的那部分汇编代码中,为啥要对ECX进行加3操作?
根据后面JZ指令是对ECX里面的值进行判断,而ECX中保存的是需要转送的数据大小。当ECX为0时,不进行数据转送,ECX小于4时,如果不进行加3操作,ECX/4的值依然为0,此时有数据但没进行转送,故需要加上3来消除这种影响。
在之前移动鼠标时会出现擦除原背景图像的错误,这里提供一种比较简单的思路。既然会将原背景图像擦除,那在绘制鼠标后,再次绘制一遍背景就可以了。
在隐藏鼠标之前,重新绘制一遍背景颜色,调用函数init_screen8()。
下图已经基本解决了擦除原图像的问题,美中不足的是还有两个小三角形的颜色一直是背景色。
修改创新点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天的内容,再结合操作系统原理理论课程的内存管理的相关内容,会对这门课程制作的操作系统更加了解。