30天自制操作系统——第八天鼠标控制与32位模式切换
今天的任务是让鼠标真正移动起来,之前的路走的真是艰辛呀。终于到走到这里啦,下面正式开始了——
鼠标解读(harib05a)
昨天我们已经能从鼠标取得数据了,接着要读取这些数据,看看鼠标是怎么移动的,在根据鼠标的动作,让鼠标指针动起来。
先对bootpack.c的HariMain函数做一些修改:
unsigned char mouse_dbuf[3], mouse_phase;enable_mouse();
mouse_phase = 0; /* 进入到等待鼠标的0xfa的状态 */for (;;) {io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {io_stihlt();} else {if (fifo8_status(&keyfifo) != 0) {i = fifo8_get(&keyfifo);io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_phase == 0) {/* 等待鼠标0xfa的状态 */if (i == 0xfa) {mouse_phase = 1;}} else if (mouse_phase == 1) {/* 等待鼠标的第一字节 */mouse_dbuf[0] = i;mouse_phase = 2;} else if (mouse_phase == 2) {/* 等待鼠标的第二字节 */mouse_dbuf[1] = i;mouse_phase = 3;} else if (mouse_phase == 3) {/* 等待鼠标的第三字节 */mouse_dbuf[2] = i;mouse_phase = 1;/* 鼠标的3字节都齐了,显示出来 */sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}}}
这段程序首先是将0xfa舍弃,每次鼠标送来的数据都是三个一组,每当数据累积到3,就显示到屏幕上。
变量mouse_phase用来记住接收鼠标数据的工作进展到了什么阶段(phase),接收到的数据存放在mouse_dbuf[0~2]中。
运行一下看看——
尝试移动鼠标,发现后面三个数字会变化。仔细观察会发现,移动鼠标时,“28”部分(mouse_dbuf[0])的“2”位,会在0~3范围内变化。
仅移动鼠标,“28”部分的“8”不会变化,只有点击鼠标时才会变化,这个值会在8~F范围内变化。“12”部分(mouse_dbuf[1])与鼠标的左右移动相关,“34”部分(mouse_dbuf[2])与鼠标的上下部分相关。
HariMain函数整理(harib05b)
HariMain函数有点乱,我们先来整理一下
struct MOUSE_DEC {unsigned char buf[3], phase;
};void enable_mouse(struct MOUSE_DEC *mdec);
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);void HariMain(void)
{(略)struct MOUSE_DEC mdec;(略)enable_mouse(&mdec);for (;;) {io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {io_stihlt();} else {if (fifo8_status(&keyfifo) != 0) {i = fifo8_get(&keyfifo);io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) {/* 3字节都凑齐了,把它们都显示出来 */sprintf(s, "%02X %02X %02X", mdec.buf[0], mdec.buf[1], mdec.buf[2]);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}}}
}void enable_mouse(struct MOUSE_DEC *mdec)
{/* 鼠标有效 */wait_KBC_sendready();io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);wait_KBC_sendready();io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);/* 顺利的话,ACK(0xfa)会被送过来 */mdec->phase = 0; /* 等待0xfa的阶段 */return;
}int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{if (mdec->phase == 0) {/* 等待鼠标0xfa的阶段 */if (dat == 0xfa) {mdec->phase = 1;}return 0;}if (mdec->phase == 1) {/* 等待鼠标第一字节的阶段 */mdec->buf[0] = dat;mdec->phase = 2;return 0;}if (mdec->phase == 2) {/* 等待鼠标第二字节的阶段 */mdec->buf[1] = dat;mdec->phase = 3;return 0;}if (mdec->phase == 3) {/* 等待鼠标第三字节的阶段 */mdec->buf[2] = dat;mdec->phase = 1;return 1;}return -1; /* 应该不可能到这里来 */
}
我们创建了一个结构体MOUSE_DEC(DEC是decode的缩写),将读取鼠标信息的各个变量都放在这个结构体里。因为鼠标已经激活了,在函数enable_mouse的最后,附加了将phase归零的处理,将读到的0xfa舍去。我们将读取鼠标信息的函数从HariMain函数里抽离出来,放到了mouse_decode函数里。
测试运行一下“make run”,没问题~
读取鼠标信息(harib05c)
首先对mouse_decode函数进行修改,bootpack.c节选:
struct MOUSE_DEC {unsigned char buf[3], phase;int x, y, btn;
};int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{if (mdec->phase == 0) {/* 等待鼠标0xfa的阶段 */if (dat == 0xfa) {mdec->phase = 1;}return 0;}if (mdec->phase == 1) {/* 等待鼠标第一字节的阶段 */if ((dat & 0xc8) == 0x08) {/* 如果第一字节正确 */mdec->buf[0] = dat;mdec->phase = 2;}return 0;}if (mdec->phase == 2) {/* 等待鼠标第二字节的阶段 */mdec->buf[1] = dat;mdec->phase = 3;return 0;}if (mdec->phase == 3) {/* 等待鼠标第三字节的阶段 */mdec->buf[2] = dat;mdec->phase = 1;mdec->btn = mdec->buf[0] & 0x07;mdec->x = mdec->buf[1];mdec->y = mdec->buf[2];if ((mdec->buf[0] & 0x10) != 0) {mdec->x |= 0xffffff00;}if ((mdec->buf[0] & 0x20) != 0) {mdec->y |= 0xffffff00;}mdec->y = - mdec->y; /* 鼠标y的方向与画图符号相反 */return 1;}return -1; /* 应该不可能到这里来 */
}
结构体里增加的变量用于存放读取的鼠标信息,这些变量分别是x、y、btn,分别用于存放移动信息和鼠标按键状态。x和y需要使用第一字节中对鼠标移动有反应的几位,将x和y的第8位及之后全部设备1,就能正确读取x和y了。在最后对符号y进行了取反操作,鼠标与屏幕的y方向正好相反,为了保持一致对y符号进行了取反操作。这样读取鼠标信息的部分就完成了,我们再来修改一下显示部分。
HariMain节选:
} else {if (fifo8_status(&keyfifo) != 0) {i = fifo8_get(&keyfifo);io_sti();sprintf(s, "%02X", i);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) {/* 数据的3个字节都齐了,显示出来吧 */sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);if ((mdec.btn & 0x01) != 0) {s[1] = 'L';}if ((mdec.btn & 0x02) != 0) {s[3] = 'R';}if ((mdec.btn & 0x04) != 0) {s[2] = 'C';}boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}
最后的三个if语句中的第一个if语句可以理解为,如果mdec.btn的最低位是1,就将小写字符置换成大写字符。
make run一下看看——
运行正常,我们移动一下鼠标。
点击一下鼠标:
移动鼠标(harib05d)
我们修改一下图形显示部分,让鼠标指针在屏幕上动起来 。
HariMain节选:
} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) {/* 数据的3个字节都齐,显示出来 */sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);if ((mdec.btn & 0x01) != 0) {s[1] = 'L';}if ((mdec.btn & 0x02) != 0) {s[3] = 'R';}if ((mdec.btn & 0x04) != 0) {s[2] = 'C';}boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);/* 鼠标指针的移动 */boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15); /* マウス消す */mx += mdec.x;my += mdec.y;if (mx < 0) {mx = 0;}if (my < 0) {my = 0;}if (mx > binfo->scrnx - 16) {mx = binfo->scrnx - 16;}if (my > binfo->scrny - 16) {my = binfo->scrny - 16;}sprintf(s, "(%3d, %3d)", mx, my);boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 隐藏坐标 */putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 显示坐标 */putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /* 描画鼠标 */}}
鼠标指针移动部分,先是隐藏鼠标指针。然后在鼠标指针的坐标上,加上得到的位移量。这里不能让鼠标指针跑到屏幕外面,进行了调整。
运行一下make run——
经历了GDT/IDT/PIC初始化、使用栈、FIFO缓冲区、处理键盘后,鼠标指针终于完成了!还挺有成就感的。
但是最后还是有点问题,还是留到后面解决吧。
分析asmhead.nas
我们之前一直都没有说明asmhead.nas中的程序,现在正好具体看一下。
asmhead.nas节选:
; PIC关闭一切中断
; 根据AT兼容机的规格、如果要初始化PIC
; 必须在CLI之前进行,否则有时会挂起
; 随后进行PIC的初始化MOV AL,0xffOUT 0x21,ALNOP ; 如果连续执行OUT指令,有些机种会无法正常运行OUT 0xa1,ALCLI ; 禁止CPU级别的中断
上面的程序等同于以下内容的C程序:
io_out(PIC0_IMR,0xff); /*禁止主PIC的全部中断*/
io_out(PIC1_IMR,0xff); /*禁止从PIC的全部中断*/
io_cli(); /*禁止CPU级别的中断*/
如果CPU进行模式转换时进来了中断信号,会带来麻烦。PIC初始化时也不允许有中断发生,因此我们把中断全部屏蔽掉。
NOP指令什么都不做,只是让CPU休息一个时钟周期。
; 为了让CPU能够访问1MB以上的内存空间,设定A20GATECALL waitkbdoutMOV AL,0xd1OUT 0x64,ALCALL waitkbdoutMOV AL,0xdf ; enable A20OUT 0x60,ALCALL waitkbdout
上段程序等同于下面的C语言程序:
#define KEYCMD_WRITE_OUTPORT 0xd1
#define KBC_OUTPORT_A20G_ENABLE 0xdf/* A20GATE的设定 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_WRITE_OUTPORT);
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_OUTPORT_A20G_ENABLE);
wait_KBC_sendready(); /*这句话是为了等待完成执行指令*/
程序的基本结构与init_keyboard完全相同,功能只是往键盘控制电路发送指令。
这里发送的指令,是指令键盘控制电路的附属端口输出0xdf。
通过这个连接主板上很多地方的附属端口,发送不同的指令,就可以实现各种各样的控制功能。
输出0xdf所要完成的功能,是让A20GATE信号线变成ON的状态,A20GATE信号线是使得内存1MB以上的部分变成可使用状态。
"wait_KBC_sendready();"是为了等待A20GATE的处理切实完成。
; 切换到保护模式[INSTRSET "i486p"] ; “想要使用486指令”的叙述LGDT [GDTR0] ; 设定临时GDTMOV EAX,CR0AND EAX,0x7fffffff ; 设bit31为0(为了禁止分页)OR EAX,0x00000001 ; 设bit0为1(为了切换到保护模式)MOV CR0,EAXJMP pipelineflush
pipelineflush:MOV AX,1*8 ; 可读写的段 32bitMOV DS,AXMOV ES,AXMOV FS,AXMOV GS,AXMOV SS,AX
INSTRSET指令,是为了能够使用386以后的LGDT、EAX、CR0等关键字。
LGDT指令,是把任意的GDT读进来。
CR0(control register 0)是一个非常重要的寄存器,只有操作系统才能操作它。
保护模式与先前的16位模式不同,段寄存器的解释操作系统受到CPU的保护,因此成为保护模式。保护模式分为两种,一种是带保护的16位模式,另一种是带保护的32位模式,我们使用的是,带保护的32位模式。在变成保护模式之后,有些地方会发生变化。变化之一是机器语言的解释方式变了。
也就是说,前一条指令还在执行的时候就开始解释下一条指令了,但是因为模式变了,就需要重新解释一遍,因此使用JMP指令。另一个变化是段寄存器的含义,不再是乘以16以后再加算的意思,而是除了CS以外所有段寄存器的值都从0x0000变成了0x0008(相当于“gdt+1”的段)。CS保持不变是为了避免混乱。
asmhead.nas节选
; bootpack的传送MOV ESI,bootpack ; 传送源MOV EDI,BOTPAK ; 传送目的地MOV ECX,512*1024/4CALL memcpy; 磁盘数据最终转送到它本来的位置去; 首先从启动扇区开始MOV ESI,0x7c00 ; 传送源MOV EDI,DSKCAC ; 传送目的地MOV ECX,512/4CALL memcpy; 所有剩下的MOV ESI,DSKCAC0+512 ; 传送源MOV EDI,DSKCAC+512 ; 传送目的地MOV ECX,0MOV CL,BYTE [CYLS]IMUL ECX,512*18*2/4 ; 从柱面数变为字节数/4SUB ECX,512/4 ; 减去IPLCALL memcpy
上边的程序也可以类似写成如下的C语言程序:
memcpy(bootpack, BOTPAK, 512*1024/4);
memcpy(0x7c00, DSKCAC, 512/4);
memcpy(DSKCAC0+512, DSKCAC+512, cyls*512*18*2/4-512/4);
函数memcpy是复制内存的函数,语法如下:
memcpy(转送源地址,转送目的地址,转送数据的大小);
转送数据大小是以双字节为单位,因此数据大小用字节数除以4来指定。
中间一条程序:
memcpy(0x7c00, DSKCAC, 512/4);
DSKCAC是0x00100000,上句的含义是从0x7c00复制到512字节,正好是将启动扇区复制到1MB以后的内存中去。
下一条:
memcpy(0x7c00, DSKCAC, 512/4);
就是讲开始于0x00008200的磁盘内容,复制到0x00100200里。
第一条:
memcpy(bootpack, BOTPAK, 512*1024/4);
这句是将bootpack.hrb复制到0x00280000号地址的处理。
接着往后看,
asmhead.nas节选:
; 必须由asmhead来完成的工作,至此全部完毕
; 以后就交由bootpack来完成 ; bootpack的启动MOV EBX,BOTPAKMOV ECX,[EBX+16]ADD ECX,3 ; ECX += 3;SHR ECX,2 ; ECX /= 4;JZ skip ; 没有要转送的东西时MOV ESI,[EBX+20] ; 转送源ADD ESI,EBXMOV EDI,[EBX+12] ; 转送目的地CALL memcpy
skip:MOV ESP,[EBX+12] ; 栈初始值JMP DWORD 2*8:0x0000001b
SHR指令是向右移位指令。
JS(jump if zero)是条件跳转指令,根据前面的结果是否为0来决定是否跳转,为0就跳转。
asmhead.nas节选:
waitkbdout:IN AL,0x64AND AL,0x02JNZ waitkbdout ; AND的结果如果不是0,就跳到waitkbdoutRET
它与wait_KBC_sendready相同,添加了从0x60号设备进行IN的处理内容。也就是说,如果控制器里有键盘代码或者鼠标代码,就把他们读出来。
asmhead.nas节选:
ALIGNB 16
GDT0:RESB 8 ; NULL selectorDW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment)32bitDW 0xffff,0x0000,0x9a28,0x0047 ; 可以执行的段(segment)32bit(bootpack用)DW 0
GDTR0:DW 8*3-1DD GDT0ALIGNB 16
bootpack:
ALIGB指令,意思是一直添加DBO,直到时机合适为止。这里如果最初地址能被16整除,ALIGB指令不做任何处理。
GDT0也是一个特别的指令,0号是空区域(null sector),不能在那里定义段。
到此为止,asmhead.nas的说明就结束了。
在最初,GDT在asmhead.nas里,而不在0x00270000~0x0027ffff。
这时IDT没有设定,仍处于中断禁止状态,这时应该放开中断接收数据。
因此在HariMain里,在调色版palette初始化和画面准备之前,先重新创建GDT和IDT,初始化PIC,并执行“io_sti()”。
HariMain函数节选:
void HariMain(void)
{struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;char s[40], mcursor[256], keybuf[32], mousebuf[128];int mx, my, i;struct MOUSE_DEC mdec;init_gdtidt();init_pic();io_sti(); /* IDT/PIC的初始化已经完成,于是开放CPU中断 */fifo8_init(&keyfifo, 32, keybuf);fifo8_init(&mousefifo, 128, mousebuf);io_out8(PIC0_IMR, 0xf9); /* 开放PIC1和键盘中断(11111001) */io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) */init_keyboard();init_palette();init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
最后看一下目前系统的内存分布图——
https://gitee.com/mint1993/myos.git
30天自制操作系统——第八天鼠标控制与32位模式切换相关推荐
- 第8天 鼠标控制与32位模式切换
第8天 鼠标控制与32位模式切换 2020.4.2 1. 鼠标解读(1)(harib05a) 现在,我们让鼠标动起来. 先对bootpack.c中的HariMain函数进行修改. unsigned c ...
- 30天自制操作系统:第8天:鼠标控制与32位模式切换
今天前半部分都是如何控制鼠标移动,将每次传送的三个字节处理成位置信息,最后指导鼠标指针移动. 后面的部分,是关于一段一直未讲解的汇编代码的讲解. 32位保护模式 书中用一连串汇编代码完成了从实模式进入 ...
- 第8天:鼠标控制与32位模式切换
8.1.鼠标解读 for (;;) {io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {io_ ...
- 索骥馆-DIY操作系统之《30天自制操作系统》扫描版[PDF]
内容简介: <30天自制操作系统>是一本兼具趣味性.实用性与学习性的操作系统图书.作者从计算机的构造.汇编语言.C语言开始解说,让读者在实践中掌握算法.在这本书的指导下,从零编写所有代码, ...
- 《30天自制操作系统》前言、目录、样章欢迎阅读!
编著推荐: 只需30天从零开始编写一个五脏俱全的图形操作系统 如果肯坚持,没有什么不可以!祝所有读到这篇文章的人都能写出好的操作系统! 内容简介: 自己编写一个操作系统,是许多程序员的梦想.也许有人曾 ...
- 30天自制操作系统第8天harib05d
第8天 鼠标控制与32位模式切换 移动鼠标指针 harib05d 准备材料(windows环境) VMware Workstation Visual Studio Code 程序和源代码:https: ...
- 30天自制操作系统——第二十三天窗口操作
窗口及输入切换 我们先来实现用键盘切换窗口,按下F11键,将最下面的窗口移动到最上面,这里F11按键的编码为0x57. bootpack.c节选: void HariMain(void) {(略)fo ...
- 【操作系统】30天自制操作系统--(9)叠加处理
这一章主要是处理之前遇到的图层叠加的问题.[操作系统]30天自制操作系统--(7)鼠标移动与32位切换 一 内存管理优化 上一章的内存管理虽然写好,但是还是有不完善的地方.因为如果不对申请内存的大小有 ...
- 30天自制操作系统第五天
操作系统实验日志5 第5天:结构体.文字显示与GDT/IDT初始化 30天自制操作系统第五天 操作系统实验日志5 一.实验主要内容 1. 内容1:接收启动信息 2. 内容2:使用结构体 3. 内容3: ...
最新文章
- 最新OCR开源神器来了!
- 数组实现栈,实现push,pop,size方法 -- 面试算法
- 初试Android基于Vuforia开发AR
- 设计一代码,逆置带头结点的动态单链表L
- docker nginx配置_docker随手笔记第十二节 jenkins+docker+nginx+纯静态页面配置
- AIML元素详细说明
- python3.4安装vc_Python3.4 用 pip 安装lxml时出现 “Unable to find vcvarsall.bat ”?
- 我们需要什么样的字段类型?
- Linus Torvalds 警告:勿用 Linux 5.12 rc1,担心供应链攻击?
- ImportError: libcudart.so.10.0: cannot open shared object file
- NodeManager节点启动报错内存不足
- OBS无延迟视频直播完整教程(组图)
- QQ快速登录实现原理分析之localhost.ptlogin2.qq.com 怎么会映射到 127.0.0.1问题
- 信用卡刷卡消费背后的故事
- oracle yyddd格式转换,Oracle PL/SQL开发基础(第二十二弹:类型转换函数)
- ML:图像数据、字符串数据等计算相似度常用的十种方法(余弦相似性、皮尔逊、闵可夫斯基距离/曼哈顿距离/欧氏距离/切比雪夫距离、马氏距离、汉明距离、编辑距离、杰卡德相似系数、相对熵/KL散度、Helli
- 电子不停车收费系统(ETC)专题(1)——系统概述
- 【校企合作】湖南工业大学计算机学院与牛耳教育签约共建实训基地
- 推荐几个好用的网盘搜索引擎
- CISSP-D8-软件开发安全
热门文章
- 2022山东老博会,山东养老展,中国国际养老服务业展9月举办
- Tensorflow的基本使用方法
- 在公交车上想出的一个java算法
- kindle上查看微信读书的书籍
- 怎样使用1M的内存排序100万个8位数
- 托福口语_新航道_刘莹_task 3 task5
- Investigating Typed Syntactic Dependencies for Targeted Sentiment Classification Using GAT(2020)
- kibana 失效原因 ,亲测有效
- 如何将数据存入mysql_怎样将数据存入mysql数据库
- 第三篇:【重磅】呼叫中心运营指标KPI字典