1. 鼠标解读
- 一个鼠标事件实际上会产生 3 个byte 的数据, 可以借助缓冲区
unsigned char mouse_dbuf[3]
, 对其数据进行输出显示 - 同时为了提高代码可读性, 采用
MOUSE_DEC
结构体对其进行封装 - 鼠标的三个字节数据保存在 buf[3] 中, 其中 buf[0] 的 低3 bit 表征鼠标的按键状态, buf[1], buf[2] 表征鼠标 x, y 的相对移动
- 鼠标移动代码:
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);
mx = (binfo->scrnx - 16) / 2; /* 画面中央になるように座標計算 */
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
sprintf(s, "(%3d, %3d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
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, "[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); /* マウス描く */
}
}
}
}
}
显示效果:
2. 通往32bit模式之路
- 这里主要分析一下 asmhead.nas 的一些内容
代码如下:
; haribote-os boot asm ; TAB=4 BOTPAK EQU 0x00280000 ; bootpackのロード先 DSKCAC EQU 0x00100000 ; ディスクキャッシュの場所 DSKCAC0 EQU 0x00008000 ; ディスクキャッシュの場所(リアルモード) ; 设定基本信息的存放地址 CYLS EQU 0x0ff0 ; ブートセクタが設定する LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; 色数に関する情報。何ビットカラーか? SCRNX EQU 0x0ff4 ; 解像度のX SCRNY EQU 0x0ff6 ; 解像度のY VRAM EQU 0x0ff8 ; グラフィックバッファの開始番地 ORG 0xc200 ; このプログラムがどこに読み込まれるのか ; 设定显卡的显示模式, 并将相应参数写入到内存的相应位置 MOV AL,0x13 ; VGAグラフィックス、320x200x8bitカラー MOV AH,0x00 INT 0x10 MOV BYTE [VMODE],8 ; 画面モードをメモする(C言語が参照する) 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 状态 MOV AL,0xff OUT 0x21,AL NOP ; OUT命令を連続させるとうまくいかない機種があるらしいので OUT 0xa1,AL CLI ; さらにCPUレベルでも割り込み禁止 ; 让 cpu 可以访问 1MB 以上的内存空间 CALL waitkbdout MOV AL,0xd1 OUT 0x64,AL CALL waitkbdout MOV AL,0xdf ; enable A20 OUT 0x60,AL CALL waitkbdout ; 切换CPU 模式为 保护模式 [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 的内容写入到 0x280000 位置处 MOV ESI,bootpack ; 転送元 MOV EDI,BOTPAK ; 転送先 MOV ECX,512*1024/4 CALL memcpy ; 将各个部分(启动扇区, IPL)拷贝到1MB 内存后面 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 JNZ waitkbdout ; ANDの結果が0でなければwaitkbdoutへ RET memcpy: MOV EAX,[ESI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 SUB ECX,1 JNZ memcpy ; 引き算した結果が0でなければmemcpyへ RET ; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける ALIGNB 16 GDT0: RESB 8 ; ヌルセレクタ 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:
- 基本流程
3. 内存分布图
4. 程序流程
下面我们来梳理下程序的运行流程
4.1 ipl10.nas
- 这部分主要负责先载入 启动扇区(0x7c00 ~ 0x7dff)
- 启动扇区载入完毕之后,将 IPL (软盘中除了启动扇区的那一块) 载入到 内存的 0x8200 ~ 0x34fff 区域(这里实际上只是载入了 10 个柱面 180kb)
- 跳转到 0xc200 处运行(这个地方实际就是 asmhead 的开始位置)
4.2 asmhead.nas
- 这部分的主要工作在之前已经提到过了
- 他将0x7c00 ~ 0x7dff 处的512byte 的启动扇区数据拷贝到 0x100000 处(1MB 空间开始位置)
- 然后将 0x8200 ~ 0x34fff 处的IPL 数据拷贝到 0x100200 处
- 同时, 将bootpack.hrb 的数据拷贝到0x280000位置处
- 同时跳转到 bootpack.hrb 处开始执行
4.3 bootpack.c
- 这部分实际就是我们 C 语言部分的操作, 用于处理键盘,鼠标中断响应, 图形绘制等操作