My Os - 鼠标
本篇文章我们要解决一个大问题,那就是鼠标。要让鼠标的移动能够实现,我们得实现完善部分系统中断。同理,中断实现了之后键盘的输入也是一样的道理了。
Day06 - 鼠标前期(中断)
显示鼠标
首先我们需要设计一个鼠标,我们用一个 16 * 16 的矩阵来表示
1 | void init_mouse_cursor8(cahr *mouse, char bc) |
上面这个函数只是鼠标的设计,还需要下面的函数把鼠标画到 VRAM
上去
1 | void putblock8_8(char *vram, int vxsize, int pxsize, |
然后只要在 HariMain
中先后调用这两个函数就可以了
1 | init_mouse_cursor8(mcursor, COL8_008484); |
GDT与IDT的初始化
GDT
是 Global (segment) Descriptor Table
的缩写。用来存放段的信息。我们把内存分成一个个段,这些段包含有以下信息:
- 段的起始地址
- 段的大小
- 段的管理属性(禁止写入,禁止执行,系统专用等)
这些属性CPU用8字节(64位)来表示,但是我们的段寄存器只有16位(实际上只有13位有效,也就是表示0~8191),所以我们需要把 8192 * 8 = 65536字节的数据存在内存的某个位置,这64KB的数据就叫做 GDT
。
IDT
则是 Interrupt Descriptor Table
的缩写,用于记录中断信息的。我们使用鼠标的时候就需要调用中断,所以我们要先设定好 IDT
。同时,如果没有写好 GDT
就写 IDT
的话会比较麻烦,所以我们顺手解决掉这个问题。
我们用结构体来存储相关信息:
1 | struct SEGMENT_DESCRIPTOR |
初始化函数:
1 | void init_gdtidt(void) |
设置函数:
1 | void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar) |
这里我们还需要一个函数把 GDT
和 IDT
里的信息移到相应寄存器里,所以我们在 naskfunc.nas
里添加以下两个函数:
1 | _load_gdtr: |
这样我们就实现了这两个表。
中断
我们完成了 IDT
的编写,下面要做的就是实现中断处理程序(代码会在后面给出,这里就举个例子)。
我们新建一个文件 int.c
里面储存各种中断处理程序
1 | void inthandler21(int *esp) |
同时我们还要在 naskfunc.nas
中增加相应的调用
1 | _asm_inthandler21: |
这样一个中断就处理好了。他的作用就是敲击键盘的时候在屏幕左上角可以显示一串字符。
代码整理
这个时候我们的 bootpack.c
就过于冗长了,我们把他拆分成四个部分
bootpack.h
:
1 | /* asmhead.nas */ |
bootpack.c
:
1 |
|
dsctbl.c
:
1 | include "bootpack.h" |
graphic.c
:
1 |
|
然后对 naskfunc.nas
和 Makefile
都作了修改。
naskfunc.nas
:
1 | ; naskfunc |
Makefile
:
1 | OBJS_BOOTPACK = bootpack.obj naskfunc.obj hankaku.obj \ |
运行结果如下:
敲击键盘前
敲击键盘后
Day07 - FIFO与鼠标控制
获取按键编码
这一步其实很简单,键盘按键编码其实会通过端口 0x0060
传入,我们只需要调用 io_in8
函数就可以得到编码了。所以我们修改一下 int.c
中的 void inthandler(int *esp)
:
1 |
|
缓冲区
但上面那种写法不好,因为一敲键盘CPU就会中断来处理键盘事物,比如你正在移动的鼠标就会卡死,所以我们需要增加缓冲区,来储存键盘的输入。最简单的方法就是用一个结构体来储存
结构体写在 bootpack.h
里:
1 | struct KEYBUF { |
更改 int.c
中的中断程序:
1 | struct KEYBUF keybuf; |
在 bootpack.c
中实现剩余功能(修改最后的for循环):
1 | for(;;) { |
这样一个简单的缓冲区就做好了。当然这个缓冲区只能储存一个字符,所以我们可以引入栈。整理一下代码如下。
fifo.c
:
1 |
|
头文件 bootpack.h
增加下面的代码,并删除之前初版缓冲区
1 | /* fifo.c */ |
然后修改 int.c
中的 void inthandler21(int *esp)
:
1 |
|
最后修改 bootpack.c
的for循环
1 | for(;;) { |
鼠标的处理和键盘如出一辙。
鼠标处理
与键盘一样我们增加鼠标的初始函数
1 |
|
然后这个时候运行可以成功在左上角显示那句话了,但是我们修改一下中断程序,直接随时读取。
1 | struct FIFO8 mousefifo; |
然后像之前写键盘那样,修改一下 bootpack.c
的for循环
1 | fifo8_init(&mousefifo, 128, mousebuf); |
第二个数字是鼠标,第一个是键盘。鼠标移动的时候第二个数字会实时变化。这样我们就能实现从鼠标获得数据了。
Day08 - 鼠标的终极控制:移动
传入数据的处理
鼠标会传入三个字节的数据,分别表示按键状态、左右移动、上下移动。和键盘一样我们用一个结构体记录这些数据。
1 | struct MOUSE_DEC { |
我们要在激活鼠标的时候为 phase
赋初值。所以要修改 enable_mouse
函数
1 | void enable_mouse(struct MOUSE_DEC *mdec) /* 增加传入的结构体参数 */ |
增加读入三字节数据的函数
1 | int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) |
修改 HariMain
1 | struct MOUSE_DEC { |
运行后显示效果如下
第一个字节 0xmn,m的数值必须在0-3这个范围内,所以这意味着该字节的第6、7两个比特位必须为0,n的值必须在8-F之间,这意味着该字节数据对应的第4个比特位必须为1。左键,滚轮,右键被按下时,n的最低3位会被置1。
第二个字节用来表示鼠标的左右移动,对该字节进行相应处理后,可以得到鼠标平移的坐标变换。
第三个字节的数据表示鼠标的上下移动,对该字节进行相应处理后,可以得到鼠标垂直移动时的坐标变换。
移动
好了,我们现在已经成功读到鼠标传入的数据了,我们下一步就是结合这些数据实现鼠标的移动了。
其实这个移动很简单,核心思想就是在鼠标原来的位置用背景色将原来的鼠标盖掉,再在新位置绘制一个鼠标。所以我们现在在鼠标结构体里增加新内容
1 | struct MOUSE_DEC { |
修改 mouse_decode
函数。我们在读第一个字节数据的时候还得先检查是否满足格式。在第三个字节数据读完之后我们再对鼠标的 x, y, btn
等信息进行完善。
1 | int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) |
最后一步就是修改 HariMain
中的for循环完成显示
1 | for (;;) { |
大功告成!
点击左键的时候变成 Lcr
,点击右键就是 Rcr
,点击滚轮就是 Ccr