当前位置: 首页 > news >正文

Day8 鼠标控制与32位模式切换

文章目录

      • 1. 例程harib05a(鼠标解读1)
      • 2. 例程harib05b(代码整理)
      • 3. 例程harib05c(鼠标解读2)
      • 4. 例程harib05d(移动鼠标指针)
      • 5. 通往32位模式之路

1. 例程harib05a(鼠标解读1)

上一章可以获取到鼠标的数据了,本章主要解读数据。首先修改一下HariMain函数的读数据处理:

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;/* 鼠标三个字节的数据集齐后显示出来 */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);}}}
}

每次从鼠标过来的数据都应该是三字节一组的,每当数据累积到三字节就显示在屏幕上。

2. 例程harib05b(代码整理)

struct MOUSE_DEC {unsigned char buf[3], phase;
};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);/* 顺利的话,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;		// 理论上不可能到这里
}/* HariMain函数 */
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) {/* 三字节集齐 */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);}}}
}

显示效果为:
在这里插入图片描述

3. 例程harib05c(鼠标解读2)

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; /* 理论上走不到这里 */
}

MOUSE_DEC 结构体中新增了x,y,btn字段用于维护移动信息和鼠标按键状态。
在等待鼠标的第一个字节阶段(if (mdec->phase == 1))中,判断了第一字节对移动有反应的部分(高4bits)是否在0 ~ 3范围内,以及判断第一字节对点击有反应的部分(低4bits)是否在8 ~ F范围内。如果不在这个范围,就被舍弃。这是一种容错机制,只是为了提高鲁棒性。

这里使用dat & 0xc8,当高4bits的值<4且低4bits的值>8时,最终运算结果才能是0x08。

在等待鼠标的第三个字节阶段(if (mdec->phase == 3))。鼠标的按键状态btn放置在buf[0]的低3bits,使用mdec->buf[0] & 0x07取出低3bits;x和y基本上可以直接使用buf[1]和buf[2],但是需要参考buf[0]中对鼠标动作有反应的几个bit位。将x和y除了低8bits外,全部都设置为1,另外鼠标与屏幕的y方向正好相反,为了配合画面方向,就对y符号进行取反操作。

/* HariMain节选 */
char s[40];} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) {/* 显示鼠标数据的三个字节 */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);}
}

s是一个字符数组,根据mdec.btn的值,将s中的“lcr”转换为大写。最终实现效果是,分别按下左键,中键和右键会使对应L,C,R字符变为大写。

4. 例程harib05d(移动鼠标指针)

同样是在HariMain函数中做修改:

} else if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);io_sti();if (mouse_decode(&mdec, i) != 0) {/* 显示鼠标的三个字节的数据 */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); /* 描画鼠标 */}
}

先隐藏鼠标,再以当前鼠标位置加上移动量作为鼠标的新坐标,并根据屏幕对鼠标进行调整,最后再描绘出鼠标图案。
在这里插入图片描述
看起来鼠标可以移动了,但是会把下边栏的图案弄花。

自此,完成了GDT/IDT/PIC的初始化,还有自由使用栈和FIFO缓冲区,以及处理键盘中断。

5. 通往32位模式之路

在asmhead.nas中,存在一些还未解释的代码:

# asmhead.nas
# PIC关闭一切中断
#	根据AT兼容机的规则,如果要初始化PIC,必须在CLI关闭中断之前进行,否则有时会挂起MOV		AL,0xffOUT		0x21,ALNOP						; 连续执行OUT,有可能会出现无法正常运行,所以NOPOUT		0xa1,ALCLI						; 禁止CPU级别的中断# 如果使用c语言,会这样写:
# io_out(PIC0_IMR, 0xff);		// 禁止主PIC的全部中断
# io_out(PIC1_IMR, 0xff);		// 禁止从PIC的全部中断
# io_cli();	// 禁止CPU级别的中断

NOP指令什么都不做,只是让CPU休息一段时间(一个时钟的时间长度)。

还有下面一段:

# asmhead.nas 节选
# 为了让CPU可以访问1M以上的内存空间,需要设定A20GATECALL	waitkbdoutMOV		AL,0xd1OUT		0x64,ALCALL	waitkbdoutMOV		AL,0xdf			; enable A20OUT		0x60,ALCALL	waitkbdout# 如果使用c语言,会这样写:
# #define KEYCMD_WRITE_OUTPUT	0xd1
# #define KBC_OUTPUT_A20G_ENABLE	0xdf
# /* A20GATE的设定 */
# wait_KBC_sendready();
# io_out8(PORT_KEYCMD, KEYCMD_WRITE_OUTPUT);
# wait_KBC_sendready();
# io_out8(PORT_KEYDAT, KBC_OUTPUT_A20G_ENABLE);
# wait_KBC_sendready();		// 为了等待完成执行指令

基本结构看起来与init_keyboard函数类似,都是往键盘控制电路发送指令。使键盘控制电路的附属端口输出0xdf。该附属端口连接主板的很多地方,通过这个端口发送不同指令可以实现很多控制功能。本次输出0xdf所要完成的功能是使能A20GATE信号线,它使内存的1M以上的部分变成可用状态。

最初的电脑,CPU只有16bits模式,所以内存最大只有1MB,后来为了兼容旧版的操作系统,在激活指令之前,电路被限制为只能使用1MB内存。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)的最高位设置为0,最低位设置位1,就完成了模式转换,进入到不使用颁的保护模式。CR0是一个特殊的寄存器,只有操作系统才能操作它。
保护模式与先前的16位模式不同,段寄存器的解释不是16倍,而是能够使用GDT。这种模式下应用程序既不能随便改变段的设定,也不能使用操作系统专用的段,操作系统收到CPU的保护。
虽然16bits模式下也有带保护的,但是我们要使用的是带保护的32bits模式。

保护模式(protected virtual address mode)全称是受保护的虚拟内存地址模式。从前的16位模式称为“real address mode”。这里的 virtual 和 real 的区别在与计算内存地址时,是使用段寄存器的值直接指定一部分,还是通过GDT使用段寄存器的值指定并非实际存在的地址号码。

通过代入CR0进入保护模式时,由于机器语言的解释会发生变化,因此需要立即执行JMP指令。CPU为了加快指令的执行速度而使用管道(pipeline)机制,即前一条指令还在执行的时候,就开始解释下一条或者更下一条指令。由于模式变了,指令需要重新解释,所以加入JMP指令。
进入保护模式后,段寄存器的意思有变化(不再是乘以16后,再加法计算的意思)。除了CS以外所有的段寄存器的值都从0x0000变为0x0008(即,gdt+1)。

memcpy:MOV		EAX,[ESI]ADD		ESI,4MOV		[EDI],EAXADD		EDI,4SUB		ECX,1JNZ		memcpyRET; 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	; 从柱面数变换为字节数/4,IMUL乘法运算SUB		ECX,512/4		; 减去IPL,SUB减法运算CALL	memcpy

这段程序主要是在调用memcpy函数:

#define 	BOTPAK	0x00280000
#define 	DSKCAC	0x00100000
#define 	DSKCAC0	0x00008000memcpy(bootpack, 	BOTPAK, 512*1024/4);
memcpy(0x7c00, 		DSKCAC, 512/4);	// 将磁盘启动扇区的512字节拷贝到0x00100000内存地址后
memcpy(DSKCAC0+512, DSKCAC+512, cyls * 512*18*2/4 - 512/4);	// 将始于0x00008200的磁盘内容拷贝到0x00100200内存地址

bootpack是asmhead.nas的最后一个标签,asmhead.bin结束的地方紧接着串联着bootpack.hrb最前面的部分。在Makefile中存在这样一句,用来表示asmhead.bin和bootpack.hrb的连接关系:

haribote.sys : asmhead.bin bootpack.hrb Makefilecopy /B asmhead.bin+bootpack.hrb haribote.sys

由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

对bootpack.hrb的头部进行解析,EBX+16表示的就是bootpack.hrb之后的第16号地址(可以使用二进制编辑器打开bootpack.hrb查看该地址内容)。
SHR是向右移位指令,ECX,2表示ECX >>= 2JZ(jump if zero)根据ECX的结果判断是否跳转。
所做的memcpy操作就是将[EBX+20]开始的[EBX+16]大小的数据拷贝到[EBX+12]。这样就开始执行bootpack.hrb了。
纸娃娃操作系统的内存分布:

0x0000 0000 ~ 0x000f ffff	: 虽然在启动中会多次使用,但是之后会变为空(1MB)
0x0010 0000 ~ 0x0026 7fff	: 用于保存软盘的内容(1440kb)
0x0026 8000 ~ 0x0026 f7ff	: 空(30kb)
0x0026 f800 ~ 0x0026 ffff	: IDT(2KB)
0x0027 0000 ~ 0x0027 ffff	: GDT(64KB)
0x0028 0000 ~ 0x002f ffff	: bootpack.hrb(512KB)
0x0030 0000 ~ 0x003f ffff	: 栈及其他(1KB)
0x0040 0000 ~ 				:

BIOS和VRAM也会存储在最低地址的1M内,并不是直接变为空。正是最初先定好了这个内存分布图,才确定了将磁盘中的内容读到0x0010 0000地址后 ,也能确认了IDT和GDT的地址。
asmhead.nas中还有几个函数没有聊到:

# waitkbdout与wait_KBC_sendready类似
waitkbdout:IN		 AL,0x64AND		 AL,0x02IN 		 AL,0x60		; 清空数据接收缓冲区的垃圾内容(书上有,代码中无)JNZ		waitkbdout		; AND的结果,如果不是0就跳转到waitkbdoutRET

最后的一部分代码:

		ALIGNB	16				; 强制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:

最初状态时,GDT在asmhead.nas里,并不在0x00270000 ~ 0x0027ffff的范围里,IDT也没有设定,所以仍旧不在禁止中断的状态。应当趁着硬件上积累过多数据而产生误动作前,放开中断,接收数据。因此,在bootpack.c的HariMain里,应该在进行调色板(palette)的初始化以及画面的准备之前,先重新创建GDT和IDT,初始化IPC,并执行io_sti();

http://www.xdnf.cn/news/190135.html

相关文章:

  • AIGC重构元宇宙:从内容生成到沉浸式体验的技术革命
  • 临床试验概述:从定义到实践的关键要素
  • R 语言科研绘图第 43 期 --- 桑基图-冲击
  • 软件设计师速通其一:计算机内部数据表示
  • 数据库学习笔记(十三)---存储过程
  • OpenCV 图形API(68)图像与通道拼接函数------垂直拼接两个图像/矩阵的函数concatVert()
  • 手搓传染病模型(SEIR-拓展)
  • 深度对比:Objective-C与Swift的RunTime机制与底层原理
  • 深入理解缓存淘汰策略:LRU 与 LFU 算法详解及 Java 实现
  • 媒资管理之视频管理
  • Prompt Engineering 提示工程:释放大语言模型潜力的关键技术与实践指南
  • C++(初阶)(十四)——多态
  • SwiftUI 10.Toggle介绍和使用
  • 马克·雷伯特:用算法让机器人飞奔的人
  • Mac搭建Flutter IOS环境详细指南
  • 了解Android studio 初学者零基础推荐(1)
  • 算术表达式通常有三种表示形式:中缀表达式、前缀表达式(波兰式)和后缀表达式(逆波兰式)。分别都是什么?
  • 【Java EE初阶】多线程(二)
  • uniapp做app,使用v-for遍历渲染第二层的时候,打包到手机上渲染不出第二层的数据
  • 如何使用极狐GitLab 议题看板?
  • LeetCode 3392、LCR106、3447题解
  • Linux学习笔记(一):Linux下的基本指令
  • 深入理解同源策略与跨域资源共享(CORS)
  • AI与IT协同的典型案例
  • C++ 解决一个简单的图论问题 —— 最小生成树(以 Prim 算法为例)
  • Shell脚本-随机数实战案例
  • 数据结构 -- 图的应用(二)
  • 机器学习中的数据转换:关键步骤与最佳实践
  • 多模态革命!拆解夸克AI相机技术架构:如何用视觉搜索重构信息交互?(附开源方案对比)
  • 讯飞星辰焕新发布!Agent规模化应用的通关密码