《操作系统30天》-合川秀实-学习日志day8

第8天:鼠标控制与32位模式切换

鼠标解读

修改的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;
					/* 鼠标的三个字节都齐了,显示出来 */
					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用来接收鼠标数据工作进展,接收到的数据放在mouse_dbuf[0-2]内。

相当于做以下处理:

  • 运行的时候移动或者滚动鼠标,会有不同的值显示。比如“08 12 34”“08”部分是mouse_dbuf[0]的0那一位,在0-3的范围内变化,如果只移动鼠标“8”不变,点击的时候8才会变化,而且变化范围在8-F之间,后面的“12”与左右移动有关系,“34”与上下移动有关系。
  • 创建结构体MOUSE_DEC ,buf[3]保存鼠标的三个字节,phase对应mouse_phase,将鼠标操作(激活、判断等)打包成函数
  • Void enable_mouse(struct MOUSE_DEC *mdec):激活鼠标,phase置0;
    Int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat):等待鼠标的phase数据,等待中断!

    修改mouse_decode函数:

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用于存放移动信息和鼠标按键状态,另外第一字节对移动有反应的部分必须要在0-3范围内,第一字节对点击有反应必须在8-F范围内,不然就舍去。这项添加是为了检查鼠标是否出了硬件问题。第三字节部分:buf[0]的低三位保存了鼠标键的状态,我们只取这三位,在显示部分做修改,低1位(001,1那位)表示按下鼠标左键显示“L”,低2位(010,同上)表示按下右键显示“R”,低3表示按下中间键显示“C”。
  • X和Y的值分别是buf[1],buf[2],考虑buf[0]的信息,作者他们的第八位及后的位全设为1。最后对Y取反,因为鼠标向下移动是Y轴正方向。
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);
				}

 

  • 移动鼠标:解读完成之后只需修改显示部分就可以了。
 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); /* 描画鼠标 */
				}
			}
  • 关于为什么要mx += mdec.x; 加的理解:mdec.x是鼠标在相对点得出的位移,有正有负,(mdec.x,mdec.y)=(0,0)表示在初始位置,其他会转换为像素坐标在左上角显示出来。根据作者做出的显示代码,还没有完善,下面的任务栏会被涂掉。

  • 32位模式
  • 解释asmhead.nas的100行代码

最开始做的事:

这段程序等同

  • 相当于系统初始化的时候(CPU模式切换、PIC初始化),禁止中断!Nop指令相当于让CPU休息一个时钟
    ;为了让CPU能访问1MB以上的内存空间,设定A20GATE
    CALL	waitkbdout
    		MOV		AL,0xd1
    		OUT		0x64,AL
    		CALL	waitkbdout
    		MOV		AL,0xdf			; enable A20,键盘控制电路附属端口输出0xdf
    		OUT		0x60,AL
    		CALL	waitkbdout

     

  • Waitkbdout相当于wait_KBC_sendready,这段相当于
  • 程序的基本结构与init_keyboard完全相同,功能是往键盘控制电路发生指令。输出0xdf的功能是让A20GATE信号线变成ON的状态:使内存的1MB以上的部分变成可使用状态。(为兼容旧版操作系统,在激活指令之前,电路被限制为只能使用1MB内存)。A20GATE信号线就是用来让这个电路停止,使所有内存可以使用。“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的值带入EAX,最高位设0,最低位设1,再把值还给CR0完成模式转换,CR0这个寄存器只有操作系统能操作它。
  • 保护模式:通过代入CR0切换到保护模式时,马上执行JMP指令,变成保护模式之后,机器语言的解释发生变化,CPU使用管道(pipeline)加速指令执行(前一条指令还在执行,就开始解释下一条指令)。保护模式中,应用程序不能随便改变段的设定,又不能使用操作系统专用的段,操作系统受到CPU的保护。进入保护模式以后,段寄存器含义发生改变,除了CS以外所有段寄存器的值都从0x0000变成0x0008,相当于gdt+1(在pipelineflush:语句下),CS保持原状,到后面再处理。
; 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

这部分程序,就是在调用memcpy函数,可以等效为下面的C代码

Memcpy函数作用是复制内存,格式为:

Memcpy(转送源地址,转送目的地址,转送数据的大小);

  • 转送数据大小是以双字为单位的,数据大小用直接数/4来指定,比如

Memcpy(0x7c00,DSKCAC,512/4);

在asmhead.nas开头查阅三个变量的值

  • DSKCAC是0x00100000,所以代码的意思是从0x7c00复制512字节到0x00100000,将启动扇区复制到1MB以后的内存去。
  • 下一句memcpy(DSKCAC0+512,DSKCAC+512,cyls * 512*18*2/4-512/4);将始于0x00008200的磁盘内容,复制到0x00100200。这一部分是启动区(512字节)之后的内容,相当于我们的asmhead。
  • “转送数据大小”的计算是以柱面数来计算的,需要减去启动区的一部分长度,这样始于0x00100000的内存部分就与磁盘内容吻合了。Bootpack是asmhead.nas的最后一个标签,haribote.sys是通过asmhead.bin和bootpack.hrb连接起来的,所以asmhead结束的地方,是bootpack.hrb开始的部分。
  • Memcpy(bootpack,BOTPAK,512*1024/4);从bootpack.hrb复制到0x00280000号地址的处理,大小为512KB。

后面的工作由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

 

  • 对bootpack.hrb的header(头部内容)进行解析,将执行所必需的的数据传送过去,EBX里代入BOTPAK:
[EBX + 16]......bootpack.hrb之后的第16号地址,值是0x11a8

[EBX + 20]......bootpack.hrb之后的第20号地址,值是0x10c8

[EBX + 12]......bootpack.hrb之后的第12号地址,值是0x00310000

 

  • ECX从0x11a8+3开始,先除4,因为memcpy函数是双字复制,一个字节2位,两个字节4位所以要除4,相当于右移两位,左移是乘。JZ跳转指令:如果计算结果为0,则跳转到skip,这里是判断ECX是否为0。这段memcpy的调用,最终实现的功能是将bootpack.hrb第0x10c8字节开始的0x11a8字节复制到0x00310000号地址去。最后将0x310000代入到ESP里,用JMP指令将2*8代入到CS里,同时移动到0x1b号地址(第2段:0x280000)。所以实际上是从0x28001b开始执行的,也就是bootpack.hrb的0x1b号地址。

最后内存分布图(确定正式GDT/IDT的地址):

最初的1MB保存有BIOS、VRAM等内容。

waitkbdout:
		IN				AL,0x64			
		AND			AL,0x02			
IN				AL,0x60			;	空读(为了清空数据接收缓冲区中的垃圾数据)
		JNZ				waitkbdout		; AND的结果如果不是0,就跳到waitkbdout
		RET
  • 这个是waitbdout完成的处理,与wait_KBC_sendready相同,添加了部分处理,从0x60号设备进行IN的处理,如果控制器里有键盘代码,或者是已经积累了鼠标数据,就把它们读取出来。

Memcpy函数:

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy			; 减法运算的结果如果不是0,就跳转到memcpy
		RET
  • 将转送的源地址放在ESI中,把ESI的值赋给EAX,然后地址往下加一位(+4),EAX的值放入目的地址(EDI)中,完成复制,EDI往下加1位(+4),每传送一次地址ECX就-1,表示完成了2个字节的复制。ECX判断是否还有数据没有复制,如果没有就重复第一步,这样一直复制直到数据传送完毕。
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:
  • ALIGNB指令的意思是一直添加DBO,直到时机合适的时候为止,在ALGNB 16的情况下,地址能被16整除的时候,就为“时机合适”,如果最初的地址能被16整除,则ALIGNB指令不作任何处理。如果标签GDT0的地址不是8的整数倍,向段寄存器复制的MOV指令就慢一些,所以使用“ALIGNB 16”,在bootpack之前,也是“时机合适”状态。

GDT0是一种特定的GDT,0号是空区域,不能在那里定义段,1/2分别这样定义

 

 

  •  DTR0是LGDT指令,通知GDT0-“已有GDT”。在GDT0里,写入16位段上限和32位的段起始地址。
  • 总结:在最初状态,GDT在asmhead.nas里,不在0x00270000~0x0027ffff的范围里,IDT没有设定,所以处于中断禁止状态。所以在bootpack.c的HariMain里,应在进行调色板(palette)的初始化以及画面的准备之前重新创建GDT和IDT,初始化PIC,并执行“io_sti();”。
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);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值