一、鼠标解读

鼠标收到激活指令后,发送的第一个按键编码是0xfa,之后,每次从鼠标发送过来的数据都是3个字节一组的。
  移动鼠标时,第一个字节的高四位会在0~3的范围内变化,也就是说第七位和第八位始终为0,鼠标向左移动时第五位为1,向下移动时第六位为1;点击鼠标时,第一个字节的低四位会在8~F之间变化,也就是说第四位始终为1,左键点击时第一位为1,右键点击时第二位为1,滚轮键点击时第三位为1。
  第二个字节与鼠标的左右移动有关,如果把一个字节当作整数的补码来看,向右移动时为正,向左移动为负,且移动速度越快绝对值越大,范围为-128~127。
  第三个字节与鼠标的上下移动有关,如果也把它作为整数的补码来解析,向上为正,向下为负。
  
bootpack.c节选

struct MOUSE_DEC {   //解读鼠标所需要的变量unsigned char buf[3], phase;    //缓冲区,鼠标阶段标志int x, y, btn;   //x和y方向移动信息,鼠标按键状态
};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) {/* 判断第一字节的第七位、第八位是否为0,第四位是否为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;mdec->btn = mdec->buf[0] & 0x07;    //取出buf[0]的低三位mdec->x = mdec->buf[1];mdec->y = mdec->buf[2];if ((mdec->buf[0] & 0x10) != 0) { //需要注意的是x和y是int型,占四个字节,而buf[0]只占一个字节,如果鼠标向左移动,则buf[0]解释为一个字节的整数为负数,扩展为四字节整数,补码要保证值不变,需要将高三个字节全部补1,也就是并0xffffff00mdec->x |= 0xffffff00;}if ((mdec->buf[0] & 0x20) != 0) {    //如果鼠标向下移动,将y的高三个字节全部补1mdec->y |= 0xffffff00;}mdec->y = - mdec->y; /* 鼠标与屏幕的y方向相反 */return 1;}return -1; /* 应该不会到这儿来 */
}

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) { //第一位是1,左键被按下s[1] = 'L';}if ((mdec.btn & 0x02) != 0) {   //第二位是1,右键被按下s[3] = 'R';}if ((mdec.btn & 0x04) != 0) {   //第三位是1,滚轮被按下s[2] = 'C';}boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 25 * 8 - 1, 31);putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);}}

二、移动鼠标指针

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); /* 描画鼠标 */}}

三、通往32位模式之路

第一次提到使用32位模式是在第三天,所以就重新复习一下之前的内容,将知识结合起来弄懂asmhead.nas到底做了些什么。
  所谓的32位模式,指的是CPU模式,CPU有16位和32位两种模式,以16位模式启动时,用AX和CX等16位寄存器会非常方便,相反,使用EAX和ECX等32位寄存器会非常麻烦,除此之外,16位和32位模式的机器语言的命令代码不一样,同样的机器语言解释的方法也不一样,所以16位模式的机器语言在32位模式下不能运行,反之亦然。
  16位模式下,如果能用一个寄存器来表示内存地址的话,由于BX只能表示0~0xffff的值,也就是只有0~65535,最大64KB,为了能访问更多内存地址,当时设计了一个起辅助作用的段寄存器,在指定内存地址时,可以使用这个段寄存器,使用段寄存器时以ES:BX这种方式来表示地址,写成“MOV AL,[ES:BX]”,代表ES×16+BX的内存地址,如果在ES和BX里都代入0xffff,也就是65535×16+65535=1M+64K-16Bytes,也就是可以指定1M以内的内存地址。
  32位模式下可以使用的内存容量远远大于1MB,另外32位下可以使用CPU的自我保护功能(识别出可疑的机器语言并屏蔽,以免破坏系统),但是在32位模式下不能调用BIOS(Basic Input Output System,基础输入输出系统)功能,这是因为BIOS是用16位机器语言写的,如果我们需要用到BIOS来做什么事情,就全部放在进入32位模式之前来做。
  我们需要BIOS做什么呢?设定画面模式、设置键盘状态、初始化段表和中断记录表,也就是第三天至今天所做的事情。

asmhead.nas

; haribote-os boot asm
; TAB=4BOTPAK  EQU     0x00280000      ; bootpack的传送目的地
DSKCAC  EQU     0x00100000      ; 磁盘传送目的地
DSKCAC0 EQU     0x00008000      ; 磁盘传送源; 有关BOOT_INFO
CYLS    EQU     0x0ff0          ; cylinders,启动区柱面数,没找到在哪里赋值过
LEDS    EQU     0x0ff1
VMODE   EQU     0x0ff2          ; 颜色的位数
SCRNX   EQU     0x0ff4          ; 分辨率的x
SCRNY   EQU     0x0ff6          ; 分辨率的y
VRAM    EQU     0x0ff8          ; 图像缓冲区的开始地址ORG     0xc200          ; 这个程序要被装载到的内存地址; 设置画面模式MOV     AL,0x13         ; VGA显卡,320×200×8位彩色MOV      AH,0x00INT      0x10            ; 调用BIOS中断设置画面模式,详情见 https://en.wikipedia.org/wiki/BIOS_interrupt_callMOV        BYTE [VMODE],8  ; 记录画面模式,赋值到第十行所指定的内存地址MOV       WORD [SCRNX],320MOV     WORD [SCRNY],200MOV     DWORD [VRAM],0x000a0000; 用BIOS取得键盘上各种LED指示灯的状态MOV       AH,0x02INT      0x16            ; keyboard BIOSMOV      [LEDS],AL       ; 将BIOS调用的返回值保存到[LEDS]; PIC(programmable interrupt controller,可编程中断控制器)关闭一切中断
;   根据AT兼容机的规格,如果要初始化PIC,必须在CLI之前进行,否则有时会挂起。
;   随后进行PIC的初始化MOV      AL,0xff         ; PIC的中断屏蔽寄存器为8位寄存器,8位对应着8路IRQ信号,如果某一位的值是1,则该值对应的IRQ信号被屏蔽,PIC就忽视该路信号OUT     0x21,AL         ; 屏蔽主PIC的全部中断NOP                        ; 让CPU休息一个时钟长的时间,如果连续执行OUT指令,有些机种会无法正常运行OUT       0xa1,AL         ; 屏蔽从PIC的全部中断CLI                        ; 禁止CPU级别的中断; 为了让CPU能够访问1MB以上的内存空间,设定A20GATECALL waitkbdout      ; 调用函数waitkbdout(在这段代码的末尾有定义)MOV      AL,0xd1OUT      0x64,AL         ; 下一个命令传送给键盘控制电路的附属端口CALL   waitkbdoutMOV       AL,0xdf         ; enable A20OUT     0x60,AL         ; 向这个附属端口发送0xdf,让A20Gate信号线变成ON的状态CALL   waitkbdout; 切换到保护模式[INSTRSET "i486p"]             ; “想要使用486指令”的叙述,之后可以使用LGDT,EAX,CR0等关键字LGDT    [GDTR0]         ; 设定临时GDT,从[GDTR0](在这段代码的末尾有定义)读取6个字节赋值给GDTR寄存器MOV     EAX,CR0         ; 将CR0(特殊的32位寄存器,只有操作系统才能操作它)的值代入EAXAND        EAX,0x7fffffff  ; 将最高位置为0OR     EAX,0x00000001  ; 将最低位置为1MOV        CR0,EAX         ; 将EAX的值返回给CR0寄存器,进入保护模式,在这种模式下,应用程序既不能随便改变段的设定,又不能使用操作系统专用的段,操作系统受到CPU的保护JMP        pipelineflush   ; 进入保护模式后机器语言的解释要发生变化,CPU使用流水线技术,在前一条命令执行的时候,就开始解释之后的命令了,变成保护模式后,要马上执行JMP指令,将解释但还未执行的命令重新解释一遍
pipelineflush:MOV       AX,1*8          ; 进入保护模式后,段寄存器的意思不再是乘16再加算了,除了CS以外的所有段寄存器的值都从0x0000变成了0x0008,CS如果变化会导致混乱,所以要放到后面再处理。0x0008相当于gdt+1的段,即段表中第1个表项的初始地址,0号是空区域,不能定义段MOV       DS,AXMOV        ES,AXMOV        FS,AXMOV        GS,AXMOV        SS,AX; bootpack的传送MOV       ESI,bootpack    ; 传送源MOV        EDI,BOTPAK      ; 传送目的地MOV      ECX,512*1024/4  ; 传送数据大小,以4字节为单位,所以数据大小要除以4CALL   memcpy          ; 调用函数memcpy(在这段代码的末尾有定义),将从bootpack的地址开始的512KB内容复制到0x00280000号地址去,这里还不理解为什么要把bootpack复制到0x00280000,它不属于磁盘吗,当前最纠结的问题就是这些文件的关系以及操作系统安装的过程; 磁盘数据最终传送到它本来的位置去; 首先从启动扇区开始MOV     ESI,0x7c00      ; 传送源MOV        EDI,DSKCAC      ; 传送目的地MOV      ECX,512/4       ; 传送数据大小为一个扇区CALL   memcpy          ; 将始于0x7c00的512字节复制到0x00100000; 所有剩下的MOV        ESI,DSKCAC0+512    ; 传送源MOV        EDI,DSKCAC+512 ; 传送目的地MOV      ECX,0MOV        CL,BYTE [CYLS]  ; 乘数IMUL    ECX,512*18*2/4  ; CYLS*512*18*2/4,CYLS为柱面数,与CL相乘之后的结果储存在ECXSUB        ECX,512/4       ; 减去IPL(Initial Program Loader,启动区)的大小CALL memcpy          ; 将始于0x00008200的CYLS*512*18*2-512字节复制到0x00100200; 必须由asmhead来完成的工作,至此全部结束
;   以后就交由bootpack来完成
; bootpack的启动MOV        EBX,BOTPAK      ; EBX赋值为bootpack.hrb的位置MOV      ECX,[EBX+16]   ; bootpack.hrb的第16号地址,值是0x11a8ADD        ECX,3           ; ECX += 3;SHR        ECX,2           ; ECX /= 4;JZ      skip            ; 如果ECX>1,那么(ECX+1)/4必不为0,如果为0跳转到skipMOV        ESI,[EBX+20]   ; 传送源,bootpack.hrb的第20号地址,值是0x10c8ADD     ESI,EBXMOV      EDI,[EBX+12]   ; 传送目的地,bootpack.hrb的第12号地址,值是0x00310000CALL  memcpy
skip:MOV        ESP,[EBX+12]   ; 栈初始值JMP       DWORD 2*8:0x0000001b    ; 2*8即CS中的第二个段,跳转到第二个段的0x1b号地址,第二个段的基址为0x280000,所以实际上是从0x28001b开始执行的,也就是bootpack.hrb的0x1b号地址; 后面的四个定义都是前面代码用到的waitkbdout:                       ; 等待键盘控制电路可以接受CPU的指令IN       AL,0x64AND      AL,0x02        ; 空读(为了清空数据接收缓冲区中的垃圾数据)JNZ        waitkbdout      ; AND的结果如果不是0,就不断循环waitkbout直至为0RETmemcpy:MOV        EAX,[ESI]ADD        ESI,4MOV        [EDI],EAXADD        EDI,4SUB        ECX,1JNZ        memcpy          ; 减法运算的结果如果不是0,就不断循环memcpyRET
; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書けるALIGNB  16
GDT0:RESB   8               ; 空区域,不能定义段DW        0xffff,0x0000,0x9200,0x00cf ; 可以读写的段DW      0xffff,0x0000,0x9a28,0x0047 ; 可以执行的段(bootpack用)DW     0
GDTR0:DW        8*3-1DD     GDT0ALIGNB  16; 接下来就开始执行bootpack.hrb了bootpack:

关于A20 GATE的补充

很多稀奇古怪的东西都是由于系统升级时,为了保持向下兼容而产生的,A20 Gate就是其中之一。
  在8086/8088中,只有20根地址总线,所以可以访问的地址是2^20=1M,但由于8086/8088是16位地址模式,能够表示的地址范围是0-64K,所以为了在8086/8088下能够访问1M内存,Intel采取了分段的模式:16位段基地址:16位偏移。其绝对地址计算方法为:16位基地址左移4位+16位偏移=20位地址。
  但这种方式引起了新的问题,通过上述分段模式,能够表示的最大内存为:FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA)。但8086/8088只有20位地址线,如果访问100000h~10FFEFh之间的内存,则必须有第21根地址线。所以当程序员给出超过1M(100000H-10FFEFH)的地址时,系统并不认为其访问越界而产生异常,而是自动从重新0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around。
  到了80286,系统的地址总线发展为24根,这样能够访问的内存可以达到2^24=16M。Intel在设计80286时提出的目标是,在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,在实模式下,80286以及后续系列,应该和8086/8088完全兼容。但最终,80286芯片却存在一个BUG:如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存,而不是象过去一样重新从0开始。
  为了解决上述问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根),被称为A20 Gate:如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。由于在当时没有更好的方法来解决这个问题,所以IBM使用了键盘控制器来操作A20 Gate,但这只是一种黑客行为,毕竟A20 Gate和键盘操作没有任何关系。在许多新型PC上存在着一种通过芯片来直接控制A20 Gate的BIOS功能。从性能上,这种方法比通过键盘控制器来控制A20 Gate要稍微高一点。
  上面所述的内存访问模式都是实模式,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。(其实所谓的实模式,就是8086/8088的模式,这种模式存在的唯一理由就是为了让旧的程序能够继续正常的运行在新的PC体系上)
引自:https://wenku.baidu.com/view/2eae56bff121dd36a32d828e.html

30天自制操作系统Day8相关推荐

  1. 为什么《30天自制操作系统》封面中的猫是两只尾巴

    刚刚在一社区,发了一贴,被指出一问题,询一高人,得一答案.这便是我没有关注到的封面上的那只猫,我想这也是很多读者没有关注到的.因为在我微博的200转发贴中,并没有人提到封面中的猫为何有两只尾巴.于是咨 ...

  2. 发布在《30天自制操作系统》之前的帮助阅读贴

    说明:这是8月15日即将上市的一本新书,本文的摘选也可以命名为<30天自制操作系统>上市之前必读.本书幽默,有趣,可以说是技术书里的幽默书,让您读起来绝对不会感到乏味.在本书上市之前,您一 ...

  3. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的 ...

  4. 写在《30天自制操作系统》上市之前

       这本<30天自制操作系统>马上就要在各大书店和网上商城全面上架了,作为本书的4位译者之一,我负责翻译了本书约三分之二的内容.这是我参与翻译的第一本译著,我感到很激动也很紧张,因为我知 ...

  5. 《30天自制操作系统》笔记(04)——显示器256色

    <30天自制操作系统>笔记(04)--显示器256色 进度回顾 从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题. 本篇做一个小练习:设置显卡显示256色. ...

  6. 《30天自制操作系统》学习笔记--第好多天

    之前看<30天自制操作系统>,参考而成,和书中系统并不完全一致,是在原有基础上按照自己的习惯而成,由于水平和工作原因,未完成内存管理和文件系统,有兴趣者可以通过以下网址https://gi ...

  7. 由《30天自制操作系统》引发的漫画创作

    大家可还记得<30天自制操作系统>的封面上的那只猫吗?记得当时,在果壳网有人问,为何这只猫长了两只尾巴呢,延着这条线,我把这本书捧上了展示的舞台.事隔四个多月,我又重提此书. 这本经我手宣 ...

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

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

  9. 30天自制操作系统-初体验

    最近在图书馆翻阅关于操作系统的书籍,看到川和秀实的自制操作系统决定也动手尝试一下,这本书书名就叫做30天自制操作系统.首先还是附上光盘镜像的获取地址吧.30天自制操作系统光盘镜像ISO完整版下载 - ...

最新文章

  1. 阿里巴巴关于Java重要开源项目汇总
  2. AMP328音频放大器
  3. ERP(Enterprise Resource Planning)
  4. 屏幕自动亮度不停的变_LCD最后的荣耀?卢伟冰:我们的屏幕比iPhone11更优秀
  5. 控制台怎么退出mysql_退出mysql控制台与退出mysql
  6. mockito环境配置_Mockito –使用全局配置的SmartNull在NPE上提供更好的错误消息
  7. vue2.X slot 分发内容
  8. 正向代理与反向代理的理解
  9. 4s测试电池的软件,只需4步,不去4S店不要专业设备,你也能测出动力电池的容量...
  10. 添加icon_(icon)冬季检查保养篇
  11. oracle驱动的区别吗,oracle10g下JDBC驱动包的区别
  12. QQlook--QQ空间密码 相册密码查看工具 国庆版 V2.4
  13. SWAT模型在水文水资源、面源污染模拟中的实践技术应用
  14. js模板引擎 html,JS 模板引擎
  15. Jupyter notebook 运行时出现 “服务似乎挂掉了,但是会立刻重启的”
  16. Java笔记(错题)
  17. 概率论与数理统计(定义定理总结)
  18. IDEA配置JPBC
  19. java中隐函数求导法则_隐函数求导的三种方法
  20. 使html轮廓颜色不同,css中border颜色不同怎么设置?

热门文章

  1. pydicom----用法一
  2. C语言结构体与结构体指针的使用
  3. 三角函数和三角函数的正交性
  4. 数字图像处理的研究方法
  5. python实现离散傅里叶变换
  6. 微型计算机的输入设备教案,高中信息技术教案设计:微型计算机的输入 输入设备1.doc...
  7. 计算机网络课程设计 文华学院,计算机网络课程设计-校园网的组建与设计.doc
  8. 九章算法面试题13 随机数生成器
  9. 2023美国大学生数学建模竞赛(MCM/ICM)报名流程指南
  10. Excel1——复制txt数据到Excel单元格并批量截取获得所需数据