IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 30天自制操作系统——第八天鼠标控制与32位模式切换 -> 正文阅读

[系统运维]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,0xff
		OUT		0x21,AL
		NOP						; 如果连续执行OUT指令,有些机种会无法正常运行
		OUT		0xa1,AL

		CLI						; 禁止CPU级别的中断

上面的程序等同于以下内容的C程序:

io_out(PIC0_IMR,0xff); /*禁止主PIC的全部中断*/
io_out(PIC1_IMR,0xff); /*禁止从PIC的全部中断*/
io_cli(); /*禁止CPU级别的中断*/

如果CPU进行模式转换时进来了中断信号,会带来麻烦。PIC初始化时也不允许有中断发生,因此我们把中断全部屏蔽掉。

NOP指令什么都不做,只是让CPU休息一个时钟周期。

; 为了让CPU能够访问1MB以上的内存空间,设定A20GATE

		CALL	waitkbdout
		MOV		AL,0xd1
		OUT		0x64,AL
		CALL	waitkbdout
		MOV		AL,0xdf			; enable A20
		OUT		0x60,AL
		CALL	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]			; 设定临时GDT
		MOV		EAX,CR0
		AND		EAX,0x7fffffff	; 设bit31为0(为了禁止分页)
		OR		EAX,0x00000001	; 设bit0为1(为了切换到保护模式)
		MOV		CR0,EAX
		JMP		pipelineflush
pipelineflush:
		MOV		AX,1*8			;  可读写的段 32bit
		MOV		DS,AX
		MOV		ES,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		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/4
		CALL	memcpy

; 磁盘数据最终转送到它本来的位置去

; 首先从启动扇区开始

		MOV		ESI,0x7c00		; 传送源
		MOV		EDI,DSKCAC		; 传送目的地
		MOV		ECX,512/4
		CALL	memcpy

; 所有剩下的

		MOV		ESI,DSKCAC0+512	; 传送源
		MOV		EDI,DSKCAC+512	; 传送目的地
		MOV		ECX,0
		MOV		CL,BYTE [CYLS]
		IMUL	ECX,512*18*2/4	; 从柱面数变为字节数/4
		SUB		ECX,512/4		; 减去IPL
		CALL	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,BOTPAK
		MOV		ECX,[EBX+16]
		ADD		ECX,3			; ECX += 3;
		SHR		ECX,2			; ECX /= 4;
		JZ		skip			; 没有要转送的东西时
		MOV		ESI,[EBX+20]	; 转送源
		ADD		ESI,EBX
		MOV		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,0x64
		AND		 AL,0x02
		JNZ		waitkbdout		; AND的结果如果不是0,就跳到waitkbdout
		RET

它与wait_KBC_sendready相同,添加了从0x60号设备进行IN的处理内容。也就是说,如果控制器里有键盘代码或者鼠标代码,就把他们读出来。

asmhead.nas节选:

		ALIGNB	16
GDT0:
		RESB	8				; NULL selector
		DW		0xffff,0x0000,0x9200,0x00cf	; 可以读写的段(segment)32bit
		DW		0xffff,0x0000,0x9a28,0x0047	; 可以执行的段(segment)32bit(bootpack用)

		DW		0
GDTR0:
		DW		8*3-1
		DD		GDT0

		ALIGNB	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

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-15 15:58:38  更:2021-08-15 15:59:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 21:55:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码