今天的任务是让鼠标真正移动起来,之前的路走的真是艰辛呀。终于到走到这里啦,下面正式开始了——

鼠标解读(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位模式切换相关推荐

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

    第8天 鼠标控制与32位模式切换 2020.4.2 1. 鼠标解读(1)(harib05a) 现在,我们让鼠标动起来. 先对bootpack.c中的HariMain函数进行修改. unsigned c ...

  2. 30天自制操作系统:第8天:鼠标控制与32位模式切换

    今天前半部分都是如何控制鼠标移动,将每次传送的三个字节处理成位置信息,最后指导鼠标指针移动. 后面的部分,是关于一段一直未讲解的汇编代码的讲解. 32位保护模式 书中用一连串汇编代码完成了从实模式进入 ...

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

    8.1.鼠标解读 for (;;) {io_cli();if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {io_ ...

  4. 索骥馆-DIY操作系统之《30天自制操作系统》扫描版[PDF]

    内容简介: <30天自制操作系统>是一本兼具趣味性.实用性与学习性的操作系统图书.作者从计算机的构造.汇编语言.C语言开始解说,让读者在实践中掌握算法.在这本书的指导下,从零编写所有代码, ...

  5. 《30天自制操作系统》前言、目录、样章欢迎阅读!

    编著推荐: 只需30天从零开始编写一个五脏俱全的图形操作系统 如果肯坚持,没有什么不可以!祝所有读到这篇文章的人都能写出好的操作系统! 内容简介: 自己编写一个操作系统,是许多程序员的梦想.也许有人曾 ...

  6. 30天自制操作系统第8天harib05d

    第8天 鼠标控制与32位模式切换 移动鼠标指针 harib05d 准备材料(windows环境) VMware Workstation Visual Studio Code 程序和源代码:https: ...

  7. 30天自制操作系统——第二十三天窗口操作

    窗口及输入切换 我们先来实现用键盘切换窗口,按下F11键,将最下面的窗口移动到最上面,这里F11按键的编码为0x57. bootpack.c节选: void HariMain(void) {(略)fo ...

  8. 【操作系统】30天自制操作系统--(9)叠加处理

    这一章主要是处理之前遇到的图层叠加的问题.[操作系统]30天自制操作系统--(7)鼠标移动与32位切换 一 内存管理优化 上一章的内存管理虽然写好,但是还是有不完善的地方.因为如果不对申请内存的大小有 ...

  9. 30天自制操作系统第五天

    操作系统实验日志5 第5天:结构体.文字显示与GDT/IDT初始化 30天自制操作系统第五天 操作系统实验日志5 一.实验主要内容 1. 内容1:接收启动信息 2. 内容2:使用结构体 3. 内容3: ...

最新文章

  1. 最新OCR开源神器来了!
  2. 数组实现栈,实现push,pop,size方法 -- 面试算法
  3. 初试Android基于Vuforia开发AR
  4. 设计一代码,逆置带头结点的动态单链表L
  5. docker nginx配置_docker随手笔记第十二节 jenkins+docker+nginx+纯静态页面配置
  6. AIML元素详细说明
  7. python3.4安装vc_Python3.4 用 pip 安装lxml时出现 “Unable to find vcvarsall.bat ”?
  8. 我们需要什么样的字段类型?
  9. Linus Torvalds 警告:勿用 Linux 5.12 rc1,担心供应链攻击?
  10. ImportError: libcudart.so.10.0: cannot open shared object file
  11. NodeManager节点启动报错内存不足
  12. OBS无延迟视频直播完整教程(组图)
  13. QQ快速登录实现原理分析之localhost.ptlogin2.qq.com 怎么会映射到 127.0.0.1问题
  14. 信用卡刷卡消费背后的故事
  15. oracle yyddd格式转换,Oracle PL/SQL开发基础(第二十二弹:类型转换函数)
  16. ML:图像数据、字符串数据等计算相似度常用的十种方法(余弦相似性、皮尔逊、闵可夫斯基距离/曼哈顿距离/欧氏距离/切比雪夫距离、马氏距离、汉明距离、编辑距离、杰卡德相似系数、相对熵/KL散度、Helli
  17. 电子不停车收费系统(ETC)专题(1)——系统概述
  18. 【校企合作】湖南工业大学计算机学院与牛耳教育签约共建实训基地
  19. 推荐几个好用的网盘搜索引擎
  20. CISSP-D8-软件开发安全

热门文章

  1. 2022山东老博会,山东养老展,中国国际养老服务业展9月举办
  2. Tensorflow的基本使用方法
  3. 在公交车上想出的一个java算法
  4. kindle上查看微信读书的书籍
  5. 怎样使用1M的内存排序100万个8位数
  6. 托福口语_新航道_刘莹_task 3 task5
  7. Investigating Typed Syntactic Dependencies for Targeted Sentiment Classification Using GAT(2020)
  8. kibana 失效原因 ,亲测有效
  9. 如何将数据存入mysql_怎样将数据存入mysql数据库
  10. 第三篇:【重磅】呼叫中心运营指标KPI字典