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

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

2020.4.2

1. 鼠标解读(1)(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) {
    				/* 等待鼠标的第1字节 */
    				mouse_dbuf[0] = i;
    				mouse_phase = 2;
    			} else if (mouse_phase == 2) {
    				/* 等待鼠标的第2字节 */
    				mouse_dbuf[1] = i;
    				mouse_phase = 3;
    			} else if (mouse_phase == 3) {
    				/* 等待鼠标的第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个字节一组的。(鼠标动一下,会产生3次鼠标中断,每次中断发送1字节数据)
    • mouse_phase用来记住接收鼠标的工作进展到了什么阶段。
    • 接收的3个字节的数据存放在mouse_dbuf数组中。
  • make run以后,点击鼠标或移动鼠标:

    • 上图中红框里面的3字节数据28 21 B3就是mouse_dbuf[0-2]中存的数据。
    • 如果移动鼠标,28(mouse_dbuf[0])中的数字2会变化,变化范围是0~3.
    • 如果只是移动鼠标28中的8并不会变化,只有当点击鼠标的时候才会变化。不管怎么点击鼠标,这个值的变化的范围是8~F.
    • 21(mouse_dbuf[1])与鼠标左右移动有关
    • B3(mouse_dbuf[2])与鼠标上下移动有关
  • 2. 稍事整理(harib05b)

  • HariMain函数显得有点乱,整理一下。

  • bootpack.c中的代码(节选):

    struct MOUSE_DEC {
        unsigned char buf[3], phase;
    };
    ……
    void enable_mouse(struct MOUSE_DEC *mdec); /*注意,这里的enable_mouse函数加了一个参数*/ 
    int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);
    ……
    void HariMain(void)
    {
        ……
        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);
                    }
                }
            }
        }
    }
    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) {
            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函数也相应地添加了一个函数。
    • 我们把鼠标解读的过程整合成了一个函数mouse_decode。这样HariMain函数看起来又就比较清爽了。
  • make run应该也没什么问题。

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

  • 再次解读鼠标

  • bootpack.c中的mouse_decode函数。

    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) {
            if (dat == 0xfa) {
                mdec->phase = 1;
            }
            return 0;
        }
        if (mdec->phase == 1) {
            if ((dat & 0xc8) == 0x08) {
                /* 如果鼠标的第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;
            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方向和屏幕的y方向相反 */
            return 1;
        }
        return -1; /* 应该不会到这里来 */
    }
    
    • 结构体MOUSE_DEC新增变量:

      • x代表鼠标移动的X方向位移。
      • y代表鼠标移动的Y方向位移。
      • btn代表按键状态。
    • mdec->phase = 1时,代码if ((dat & 0xc8) == 0x08)用于判断第一个字节对移动有反应的部分(56位)是否在03的范围内,同时判断第一个字节对点击有反应的部分(13位)是否在8F的范围内。

      • 只有当dat=00xx1xxx的时候if语句才为真。此时,dat的对应位必然在对应范围内。
      • 这样做是因为,防止偶尔鼠标出错。
    • mdec->phase = 3时,鼠标按键的状态存放在mdec->buf[0]的低3位。因此只需要按位与0x07即可得到btn。

    • 对于x和y的获得,直接使用mdec->buf[1]和mdec->buf[2]即可。同时,需要根据第1字节中对鼠标移动有反应的几位信息,将x和y的第8位以及第8位以后全部设成1,或者全部保留为0.

      • mdec->buf[0]:00xx1xxx。高4位的00xx是有关鼠标移动的。其中第5位的x如果是1,代表鼠标在X方向有变化,即x值需要更新。第6位的x如果是1,代表鼠标在Y方向上有变化,即y值需要更新。
    • 最后,再注意一下,鼠标的Y方向和屏幕的Y方向是相反的,因此y的值需要取其相反数。

  • 修改bootpack.c中的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_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);
    			}
    		}
    	}
    }
    
    • mdec.btn的低3位abc:
      • 如果第1位是1,代表鼠标左键被按下。
      • 如果第2位是1,代表鼠标右键被按下。
      • 如果第3位是1,代表鼠标中键被按下。
    • 这里再提一下boxfill8函数,它的作用是用特定的颜色画一个矩形。
      void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
      {  
      	int x, y;
          for (y = y0; y <= y1; y++) {
              for (x = x0; x <= x1; x++)
                  vram[y * xsize + x] = c;
          }
          return;
      }
      
      • 矩形范围是(x0, y0)-(x1, y1)。
  • make run:

    • 移动鼠标:
    • 点击鼠标左键:

      还不错哦。

4. 移动鼠标指针(harib05d)

  • 因为鼠标解读已经完成了,现在只需要改一下图形显示,这样就能让鼠标在屏幕上动起来了。

  • bootpack.c中的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); /* 描画鼠标 */
        }
    }
    
    • 因为鼠标要移动,所以现需要将鼠标用boxfill8函数隐藏,然后重新计算鼠标的位置(不能超出屏幕范围),然后重新绘制鼠标。由于鼠标位置也得更新,所以坐标显示也得先隐藏后显示。
  • 这里说明一下,make run使用QEMU运行系统时会产生一个错误(这个错误从第7天的harib04a就已经产生了),就是按下一个按键字母(比如A),会一直产生键盘中断。使用VMware便不会产生这样的情况。所以,在不用到键盘按键的时候我们使用QEMU运行,否则使用VMware。

  • 使用VMware运行:

    鼠标终于能动起来了。
    经过了伊凡‘苦战’:完成了GDT/IDT/PIC的初始化,使用了FIFO缓冲区,学会了处理键盘中断。

  • 由于没有考虑到叠加处理,我们在移动鼠标的时候回产生如下‘尴尬’情形。

5. 通往32位模式之路


现在是2020.4.7 10:03,我又重新开始进行毕业设计相关的工作了。
从4.2到今天过去四天了。从4.2号起,腰痛得厉害,感觉要断了,贴了膏药也无济于事。椅子太差劲,坐在上面导致腰痛是必然。于是连夜买了桌椅套装和按摩器。趁着清明放假,我也给自己放个假期。昨天桌椅到了,傍晚趁着晚霞,装起来了桌椅。现在,坐在新桌椅上,用按摩器按摩着腰。


  • 第3天中,进入32位模式时,源代码asmhead.nas并未解释。

  • harib05d中的asmhead.nas:

    ; haribote-os
    ; TAB=4
    
    BOTPAK	EQU		0x00280000		
    DSKCAC	EQU		0x00100000		
    DSKCAC0	EQU		0x00008000		
    
    ; 有关BOOT_INFO
    CYLS	EQU		0x0ff0			; 设置启动区,从内存中获取CYLS
    LEDS	EQU		0x0ff1
    VMODE	EQU		0x0ff2			; 关于颜色数目的信息。颜色的位数。
    SCRNX	EQU		0x0ff4			; 分辨率X(screen x)
    SCRNY	EQU		0x0ff6			; 分辨率Y(screen y)
    VRAM	EQU		0x0ff8			; 图像缓冲区的开始地址
    
            ORG		0xc200			; 设置程序装载地址。
            MOV		AL,0x13			; VGA显卡,320*200*8位彩色
            MOV		AH,0x00
            INT		0x10
            MOV		BYTE [VMODE],8	; 记录画面模式
            MOV		WORD [SCRNX],320
            MOV		WORD [SCRNY],200
            MOV		DWORD [VRAM],0x000a0000
    
    ; 用BIOS获取键盘上各种LED指示灯的状态。
            MOV		AH,0x02
            INT		0x16 			; keyboard BIOS
            MOV		[LEDS],AL  
    
    ; PIC关闭一切中断
    ;	根据AT兼容机的规格,如果初始化PIC
    ;	必须在CLI之前进行,否则有时会挂起
    ;	随后进行PIC的初始化
    
            MOV		AL,0xff
            OUT		0x21,AL
            NOP						; 如果连续执行OUT指令,有些机种会无法正常运行
            OUT		0xa1,AL
    
            CLI						; 禁止CPU级别的中断
    
    ; 为了让CPU能够访问1MB以上的内存空间,设定A20GATE
    
            CALL	waitkbdout
            MOV		AL,0xd1
            OUT		0x64,AL
            CALL	waitkbdout
            MOV		AL,0xdf			; enable A20
            OUT		0x60,AL
            CALL	waitkbdout
    
    ; 切换到保护模式
    
    [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
    
    ; 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
    
    ; 必须由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
    
    waitkbdout:
            IN		 AL,0x64
            AND		 AL,0x02
            IN   	 AL,0x60        ; 空读(为了清空数据接收缓冲区中的垃圾数据)
            JNZ		waitkbdout		; AND的结果如果不是0,就跳到waitkdbout
            RET
    
    memcpy:
            MOV		EAX,[ESI]
            ADD		ESI,4
            MOV		[EDI],EAX
            ADD		EDI,4
            SUB		ECX,1
            JNZ		memcpy			; 减法运算的结果如果不是0,就跳到memcpy
            RET
    
            ALIGNB	16
    GDT0:
            RESB	8				; NULL selector
            DW		0xffff,0x0000,0x9200,0x00cf	; 可读写的段 32bit
            DW		0xffff,0x0000,0x9a28,0x0047	; 可执行的段 32bit(bootpack用)
    
            DW		0
    GDTR0:
            DW		8*3-1
            DD		GDT0
    
            ALIGNB	16
    bootpack:
    
    
  • 代码:

    ; PIC关闭一切中断
    ;	根据AT兼容机的规格,如果初始化PIC
    ;	必须在CLI之前进行,否则有时会挂起
    ;	随后进行PIC的初始化
    
            MOV		AL,0xff
            OUT		0x21,AL
            NOP						; 如果连续执行OUT指令,有些机种会无法正常运行
            OUT		0xa1,AL
    
            CLI						; 禁止CPU级别的中断  
    

    相当于代码:

    io_out8(PIC0_IMR, 0xff); /* 禁止主PIC的全部中断 */
    io_out8(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
    

    相当于代码:

    #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_KEYDAT, KBC_OUTPORT_A20G_ENABLE);
        wait_KBC_sendready();
    
    • 这里的waitkbdout等同于wait_KBC_sendready【以后还会说明】。
    • waitkbdout的代码待会儿有解释。
  • 代码:

     ; 切换到保护模式
    
    [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读进来。对于这个暂定的GDT,我们以后还要重新设置
    • 然后将CR0这一特殊的32位寄存器的值代入EAX中,并将最高位设置为0,最低位设置为1,再将这个值写回CR0寄存器。这样就完成了模式转换,进入了不用分页的保护模式。 CR0,control register 0, 是一个非常重要的寄存器,只有OS能操作它。
  • 代码:

    ; 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函数。

    • 程序相当于代码:

      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(转送内存源地址,转送内存目的地址,转送数据的大小)
      
      • 转送数据的大小是以双字(DOWRD),所以数据大小要用字节数除以4来指定。
      • ESI中存放源地址
      • EDI中存放目的地址
      • ECX中存放转送数据大小
    • 常量定义:

      BOTPAK	EQU		0x00280000		
      DSKCAC	EQU		0x00100000		
      DSKCAC0	EQU		0x00008000	
      
    • 先看memcpy(0x7c00, DSKCAC, 512/4);

      • DSKCAC是0x00100000,这句话的意思是,从内存地址0x7c00复制512字节到内存地址0x100000中去。 也就是说,将启动扇区的512字节复制到1MB以后的内存中去。
    • 再看memcpy(DSKCAC0+512, DSKCAC+512, cyls*512*18*2/4 - 512/4);

      • DSKCAC0是0x8000,这句代码的意思是:从内存地址为0x8200复制x字节到0x100200去。
      • x是(cyls51218*2/4 - 512/4)*4。
      • 这样,始于0x100000的内存,就和磁盘的内容吻合了。
      • IMUL是乘法运算,SUB是减法运算。
    • 最后看memcpy(bootpack, BOTPAK, 512*1024/4);

      • bootpack是asmhead.nas的最后一个标签。通过Makefile文件可以知道,haribote.sys是由asmhead.bin和bootpack.hrb连接起来的,所以asmhead结束的地方,紧接着就是bootpack.hrb最前面的部分。
      • 这句代码的意思是:从内存地址bootpack复制512*1024个字节(512KB)到内存地址0x280000中去。
      • 512字节比bootpack.hrb大得多,这是因为多一点防止出错。
  • 代码: 存疑5

     ; 必须由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
    
    • 我们依然是在做memcpy。它对bootpack.hrb的header进行解析,将执行所必须的数据传送过去。EBX中带入的是BOTPAK,所以值如下:

      这些值因harib的版本不同而不同。
      比如,在harib05d中,这些值分别是:
      • [EBX+16]-----bootpack.hrb之后的第16号地址,值为0x000011a8.(高地址高位,小端模式。)
      • [EBX+20]-----bootpack.hrb之后的第20号地址,值为0x00001104.
      • [EBX+12]-----bootpack.hrb之后的第12号地址,值为0x00310000.
    • SHR指令是右移位指令
    • JZ, jump if zero,根据前一个计算结果是否为0来决定是否跳转。如果是0,则跳转,如果不是0则不跳转。
    • 那么,ESI=0x00001104,EDI=0x00310000,ECX=0x000011a8。
    • 所以,skip之前的代码的意思是:将bootpack.hrb第0x1104字节开始的0x11a8字节复制到0x310000号地址去。
    • 这里显得很奇怪,后面还会解释。
    • skip代码:
      • 将0x3100000带入ESP中
      • 使用特殊的JMP指令,将2*8代入到CS中,同时移到0x1b号地址。这里的0x1b号地址是指第2个段的0x1b号地址。第2个段的基地址是0x280000,所以实际上是从0x28001b开始执行的。这也就是bootpack.hrb的0x1b号地址。
      • 我们在设计时,bootpack使用的就是第2个段,(第1个段是全部内存),起始地址就是0x280000。
  • 内存分布图

    • 0x00000000 - 0x000fffff : 在启动时多次使用,之后变空。(1MB)
    • 0x00100000 - 0x00267fff : 用于保存软盘内容。(1440KB)
    • 0x00268000 - 0x0026f7ff : 空。(30KB)
    • 0x0026f800 - 0x0026ffff : IDT。(2KB)
    • 0x00270000 - 0x0027ffff : GDT。(64KB)
    • 0x00280000 - 0x002fffff : bootpack.hrb。(512KB)
    • 0x00300000 - 0x003fffff : 栈及其他。(1MB)
    • 0x00400000 - : 空。

    其中,0x00000000 - 0x000fffff部分的细分:

    • 0x00000000 - 0x00007bff : 被占用,但是具体是干什么的现在未知,但有一部分是关于VRAM的。(31KB)
    • 0x00007c00 - 0x00007dff : 用于启动区。(512B)
    • 0x00008000 - 0x000081ff : 启动区使用。(512B)
    • 0x00008200 - 0x00034fff : 软盘内容,10个柱面。(180KB)
    • 0x00034fff - 0x000fffff : 未知。(812KB)
  • 代码:

    waitkbdout:
            IN		 AL,0x64
            AND		 AL,0x02
            IN   	 AL,0x60        ; 空读(为了清空数据接收缓冲区中的垃圾数据)
            JNZ		waitkbdout		; AND的结果如果不是0,就跳到waitkdbout
            RET
    
    • waitkbdout和wait_KBC_sendready差不多。但是添加了从设备号0x60的设备上进行IN的处理,也就是说,如果控制器里有键盘代码或者积累了鼠标数据,就顺便将它们读出来。
      #define PORT_KEYSTA				0x0064
      #define KEYSTA_SEND_NOTREADY	0x02
      
      void wait_KBC_sendready(void)
      {
          /* 等待键盘控制电路准备就绪 */
          for (;;) {
              if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
                  break;
              }
          }
          return;
      }
      
    • JNZ,和JZ相反,意思是jump if not zero.
  • 代码:

    memcpy:
            MOV		EAX,[ESI]
            ADD		ESI,4
            MOV		[EDI],EAX
            ADD		EDI,4
            SUB		ECX,1
            JNZ		memcpy			; 减法运算的结果如果不是0,就跳到memcpy
            RET
    
    • 复制内存的程序。
    • ESI中存放源开始地址;EDI中存放目的开始地址;ECX中存放的是数据大小(因为ECX是32位寄存器,所以字节数/4).
  • 代码:

            ALIGNB	16
    GDT0:
            RESB	8				; NULL selector
            DW		0xffff,0x0000,0x9200,0x00cf	; 可读写的段 32bit
            DW		0xffff,0x0000,0x9a28,0x0047	; 可执行的段 32bit(bootpack用)
    
            DW		0
    GDTR0:
            DW		8*3-1
            DD		GDT0
    
            ALIGNB	16
    bootpack:
    
    
    • ALIGNB:align是排整齐的意思。ALIGNB的意思是,一直添加DBO(空指令,还是DB 0?),存疑6
      直到‘时机合适’为止。
      ALIGNB 16的情况下,地址能被16整除的时候,就称为“时机合适”。如果最初的地址能被16整除,那么ALIGNB指令不做任何处理。

    • GDT0也是一种特定的GDT。0号是空区域(null sector),
      不能够在那里定义段。1号和2号分别由下面代码设定:

      #define LIMIT_BOTPAK	0x0007ffff
      #define ADR_BOTPAK		0x00280000
      #define AR_DATA32_RW	0x4092
      #define AR_CODE32_ER	0x409a
      
      set_segmdesc(gdt + 1, 0xffffffff,   0x00000000, AR_DATA32_RW);
      set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
      
      • 相当于代码:
        set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
        set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
        
      • 查看函数set_segment:
        void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
        {
            if (limit > 0xfffff) {
                ar |= 0x8000; /* G_bit = 1 */
                limit /= 0x1000;
            }
            sd->limit_low    = limit & 0xffff;
            sd->base_low     = base & 0xffff;
            sd->base_mid     = (base >> 16) & 0xff;
            sd->access_right = ar & 0xff;
            sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
            sd->base_high    = (base >> 24) & 0xff;
            return;
        }
        
      • 查看CPU有关GDT的设定图
      • set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);注意:0xffffffff > 0xfffff, 那么limit = 0xfffff, ar = 0xc092。具体8字节怎么填,按照查看CPU有关GDT的设定图填即可。最终应该写的8字节数据就是:0xffff 0x0000 0x9200 0x00cf
      • 同理,set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);按照查看CPU有关GDT的设定图,最终的8字节数据就是0xffff 0x0000 0x9a28 0047
      • 这样,DW后面的数据也就明朗了。
    • GDTR0是LGDT指令,意思是通知GDT0说:“有GDT了哦。”代码的意思是,写入了16位的上限(8*3-1),和32位的段起始地址(GDT0)

    • 再熟悉一下DB,DW,DD:

      • DB:往内存中写入8位数据(1字节)。
      • DW:往内存中写入16位数据(2字节,1字)
      • DD:往内存中写入32位数据(4字节,双字)
  • 到此为止,asmhead.nas的解释就完成了。也就是说,最初状态时,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);
        ……
    

6. 腰痛

  • 腰痛不是一个好信号。
  • 缺乏锻炼,一坐就是几个小时,椅子不好。这些都是腰痛的原因。
  • 今晚的膏药起了作用,腰间凉凉的,有点舒服的感觉。
  • 在手机上设了闹钟,每45min响一次,提醒自己改休息一下。
  • 现在是2020.4.7 22:30。夜已经深了,父母早已睡下。
  • 加油,明天继续。第9天,内存管理。
  • 这次Markdown代码的行数是825行,又’长’了一点儿(笑)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值