30日OS自作入門8日目(Win10)
初めに
最近基礎が少しついたのか、ソースコードから大体の処理内容が分かるようになってきたんですよ!自作OS本様様です。なので、これからは記事の記述の大部分をコメントとして、ソースコードに乗せることで、スピードを上げていきます。
目次
- 初めに
- 目次
- マウス制御ト32ビットモードの切り替え
- マウスの解読(1)(harib05a)
- ちょっと整理(harib05b)
- マウスの解読(2)(harib05c)
- 動けマウス(harib05b)
- 32ビットモードへの道
- 感想
マウス制御ト32ビットモードの切り替え
マウスの解読(1)(harib05a)
コードの修正は、以下のように「最初のデータである0xfaを読み捨てる処理」・「3バイトずつ送られてくるデータを1、2、3バイト目のデータをそれぞれ1バイトの配列で受け取る処理」にif文で切り替えをしているコードです。
enable_mouse(); /* マウス有効化処理 */ mouse_phase = 0; /* マウスから0xfaが来た状態を記録 */ for (;;) { io_cli(); /* 外部割り込み禁止(割り込み処理中の割り込み対策) */ if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { /* どちらからもデータが来てないことの確認 */ io_stihlt(); /* 外部割り込みの許可と、CPU停止命令(割り込みの終了) */ } else { if (fifo8_status(&keyfifo) != 0) { /* もしキーボードの方のデータが来ていたら */ i = fifo8_get(&keyfifo); /* キーコードのアドレスを変数iに格納 */ io_sti(); /* IFに1をセット(外部割り込みの許可) */ 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に1をセット(外部割り込みの許可) */ if (mouse_phase == 0) { /* 0xfaが届くまで待機 */ /* マウスの0xfaを待っている段階 */ if (i == 0xfa) { /* 0xfaが届いたら */ mouse_phase = 1; /* マウスのデータのステータスを1に変更 */ } } else if (mouse_phase == 1) { /* マウスのデータのステータスが1ならば(マウスボタン) */ /* マウスの1バイト目を待っている段階 */ mouse_dbuf[0] = i; /* マウスのデータバッファに1バイト目を格納 */ mouse_phase = 2; /* マウスのデータのステータスを2に変更 */ } else if (mouse_phase == 2) { /* マウスのデータのステータスが2ならば(x座標) */ /* マウスの2バイト目を待っている段階 */ mouse_dbuf[1] = i; /* マウスのデータバッファに2バイト目を代入 */ mouse_phase = 3; /* マウスのデータのステータスを3に変更 */ }else if (mouse_phase == 3) { /* マウスのデータのステータスが3ならば(y座標) */ /* マウスの3バイト目を待っている段階 */ mouse_dbuf[2] = i; /* マウスのデータバッファに3バイト目を代入 */ mouse_phase = 1; /* マウスのデータのステータスを1に変更(完了) */ /* データが3バイト揃ったので表示 */ sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]); /* それぞれのデータを文字列sに格納 */ 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); /* 文字の描写 */ } } }
このように、mouse_dbuf
でデータを整理し、mouse_phase
でデータの種類を分けています。
実行すると
このように3バイトをそれぞれ表示することができます。
ちょっと整理(harib05b)
Harimainがごちゃごちゃしているので整理するようです。 構造体MOUSE_DEC
にデータをまとめ、0xfaをenable_mouse側で処理してもらうように変更されています。また、マウスの解読処理は関数mouse_decode
にまとめられています。
マウスの解読(2)(harib05c)
2つ目のマウス解読は3バイトのデータをそれぞれx座標、y座標、クリック等のマウスの状態に変換しています。
まずは、mouse_decode
で配線が切れていた時などにデータが途切れるかもしれないことの対策として
if (mdec->phase == 1) { /* ステータスがマウスからのデータ受信が可能になっていたら */ /* マウスの1バイト目を待っている段階 */ if ((dat & 0xc8) == 0x08) { /* 0~3桁目と8~F桁目以外をマスクし、ほしいデータがやってきているかを確認 */ /* 正しい1バイト目だった */ mdec->buf[0] = dat; /* バッファにデータを格納し */ mdec->phase = 2; /* 次の段階にステータスを更新 */ } return 0;
次に、x座標とy座標を正確に抽出する
if (mdec->phase == 2) { /* 1バイト目のデータをバッファに格納しているならば */ /* マウスの2バイト目を待っている段階 */ mdec->buf[1] = dat; /* バッファに2バイト目のデータを格納し */ mdec->phase = 3; /* 次の段階にステータスを更新 */ return 0; } if (mdec->phase == 3) { /* 2バイト目までのデータを受け取っているならば */ /* ますの3バイト目を待っている段階 */ mdec->buf[2] = dat; /* バッファに3バイト目のデータを格納し */ mdec->phase = 1; /* 次の段階にステータスをリセット */ mdec->btn = mdec->buf[0] & 0x07; /* 下位3ビットがボタンのデータなのでマスクする */ mdec->x = mdec->buf[1]; /* バッファからメンバxへx座標を格納 */ mdec->y = mdec->buf[2]; /* バッファからメンバyへy座標を格納 */ if ((mdec->buf[0] & 0x10) != 0) { /* マウスのステータスの1バイト目のデータを使って */ mdec->x |= 0xffffff00; /* 8ビットより上を1にする */ } if ((mdec->buf[0] & 0x20) != 0) { /* マウスのステータスの1バイト目のデータを使って */ mdec->y |= 0xffffff00; /* 8ビットより上を1にする */ } mdec->y = - mdec->y; /* マウスではy方向の符号が画面と反対なので */ return 1; }
これによりデータの整理が完了
if (mouse_decode(&mdec, i) != 0) { /* データが3バイト揃ったので表示 */ sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); /* それぞれのデータを文字列sに格納 */ if ((mdec.btn & 0x01) != 0) { /* 左ボタンのデータ以外のデータをマスクして判定 */ s[1] = 'L'; /* lを大文字に */ } if ((mdec.btn & 0x02) != 0) { /* 右ボタン以外のデータをマスクして判定 */ s[3] = 'R'; /* rを大文字に */ } if ((mdec.btn & 0x04) != 0) { /* 中ボタン以外のデータをマスクして判定 */ s[2] = 'C'; /* 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); /* 文字の描写 */ }
これで、ボタン関連のデータの解読も完了します。
実行すると
おお!わかりやすくなった!
動けマウス(harib05b)
ついにマウスが動かせる!!!!楽しみでたまらないです!
コード自体は簡単らしく(実際簡単だった)
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15); /* マウスを消す */ mx += mdec.x; /* マウスのx座標にx座標の移動量を追加 */ my += mdec.y; /* マウスのy座標にy座標の移動量を追加 */ if (mx < 0) { /* 左端に来たら */ mx = 0; /* x座標を維持し続ける */ } if (my < 0) { /* 上端に来たら */ my = 0; /* y座標を維持し続ける */ } if (mx > binfo->scrnx -16) { /* 一番右の長さからマウスの大きさ分を引いた点に来たら */ mx = binfo->scrnx -16; /* x座標を維持し続ける */ } if (my > binfo->scrny -16) { /* 一番下の位置からマウスの大きさ分を引いた点に来たら */ my = binfo->scrny -16; /* y座標を維持し続ける */ } sprintf(s, "(%3d, %3d)", mx, my); /* 指定アドレスのデータを文字列にしsに格納 */ 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); /* マウスの描写 */
感じた点は、
・渡されたデータを3バイトごとに再描画している
・これでは重ね合わせ処理をしないと、1色しか表示できない
といったところです。重ね合わせ処理をどうやるか楽しみです!
32ビットモードへの道
32ビットモードに移行するときの説明を端折った分を解説する内容でした。
説明としてはasmhead.asm
の```bootpack.cに処理が移るまでに何を行っているかの説明となっていました。
; PICが一切の割り込みを受け付けないようにする ; AT互換機の仕様では、PICの初期化をするなら、 ; こいつをCLI前にやっておかないと、たまにハングアップする ; PICの初期化はあとでやる MOV AL,0xff ; ALに0xffを代入 OUT 0x21,AL ; PIC0_IMRを指定して0xffコマンド(割り込み停止) NOP ; OUT命令を連続させるとうまくいかない機種があるらしいので(1クロック休憩) OUT 0xa1,AL ; PIC1_IMRを指定して0xffコマンド(割り込み停止) CLI ; さらにCPUレベルでも割り込み禁止
CPUのモードを変更するために、割り込み禁止を出しています。
; CPUから1MB以上のメモリにアクセスできるように、A20GATEを設定 ; 16ビットCPUのメモリから32ビットCPUのメモリの大きさに変化 CALL waitkbdout ; KBCコマンド出力待機 MOV AL,0xd1 ; ALにポート番号を格納 OUT 0x64,AL ; アウトプットポートに書き込み CALL waitkbdout ; KBCコマンド出力待機 MOV AL,0xdf ; enable A20(A20アドレスライン有効化コマンド) OUT 0x60,AL ; キーボードエンコーダーにコマンドを送信 CALL waitkbdout ; KBCコマンド出力待機
KBCにコマンドを送り、A20GATE信号線というものをONにしています。これで、メモリが1MBの制限から解き放たれます
; プロテクトモード移行(保護あり仮想メモリモード) [INSTRSET "i486p"] ; 486命令まで使いたいという記述 LGDT [GDTR0] ; 仮のGDTを読み込み MOV EAX,CR0 ; コントロールレジスタ0のデータをEAXに読みだす AND EAX,0x7fffffff ; bit31を0にする(ページング禁止のため) OR EAX,0x00000001 ; bit0を1にする(プロテクトモード移行のため) MOV CR0,EAX ; コントロールレジスタ0にステータスを戻す JMP pipelineflush ; 機械語が変化することによるJMP命令(必須) pipelineflush: ; パイプラインがかかわっているので MOV AX,1*8 ; AXに0x0008を代入 MOV DS,AX ; データセグメントをプロテクトモードに対応 MOV ES,AX ; エクストラセグメントをプロテクトモードへ対応 MOV FS,AX ; Fセグメントをプロテクトモードへ対応 MOV GS,AX ; Gセグメントをプロテクトモードへ対応 MOV SS,AX ; スタックセグメントをプロテクトモードへ対応 ; CSは後回し(混乱するらしい)
ここで32ビットに切り替わります。
・コントロールレジスタのフラグを変えることによりセグメント方式の指定やプロテクトモードの設定を行っている。
・32ビットモード移行にあたり、命令の解釈の違いでパイプライン処理に影響が出ないようにする処理
・CS以外のセグメントレジスタをプロテクトモードのアドレスに対応させる
*プロテクトモードとは、日本語で「仮想アドレス保護モード」と呼ぶ、これが設定されることで仮想記憶、マルチタスクやページング等も設定できるようになる。
; bootpackの転送 MOV ESI,bootpack ; 転送元 MOV EDI,BOTPAK ; 転送先 MOV ECX,512*1024/4 ; 転送サイズ(ダブルワードなので1/4) CALL memcpy ; データをメモリ上で上の3命令で指定したデータに従い転送 ; ついでにディスクデータも本来の位置へ転送 ; まずはブートセクタから MOV ESI,0x7c00 ; 転送元 MOV EDI,DSKCAC ; 転送先 MOV ECX,512/4 ; サイズ指定 CALL memcpy ; メモリ上でのデータ転送 ; 残り全部 MOV ESI,DSKCAC0+512 ; 転送先 MOV EDI,DSKCAC+512 ; 転送元 MOV ECX,0 ; 下位4バイトのみのデータにするためにリセット 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 ; 転送すべきものがない(JZは直前の計算結果が0の場合) 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の0x1b番地にジャンプ(特別なJMP命令らしい) ; 詳細は書籍のメモリマップを見たらわかりやすい
はりぼてアプリケーションのための設定
waitkbdout: IN AL,0x64 ; キーボードにたまっていたキーボード、マウスのデータの受け取り AND AL,0x02 ; データがまだあるかの確認? IN AL,0x60 ; 空読み(受信バッファが悪さをしないように) JNZ waitkbdout ; ANDの結果が0でなければwaitbdoutへ RET
キーボードやマウスにデータがたまっていないかと、受けとる処理
memcpy: ; メモリを4バイトずつコピーするラベル MOV EAX,[ESI] ; 転送元のデータをEAXに格納 ADD ESI,4 ; アドレスを4進める MOV [EDI],EAX ; メモリのデータを送信先に格納 ADD EDI,4 ; アドレスを4進める SUB ECX,1 ; データのサイズをマイナス1する JNZ memcpy ; 引き算した結果が0でなければmemcpyへ RET ; memcpyはアドレスサイズプリフィクスを入れ忘れなければ、ストリング命令でも書ける
メモリ上のデータを別のアドレスにコピーする仕組み
EAXを利用して、4バイトずつせっせと転送してる
ALIGNB 16 ; GDT0のアドレスをプロテクトモードに伴い8の倍数に修正 GDT0: ; RESB 8 ; nullセクタなので飛ばす DW 0xffff,0x0000,0x9200,0x00cf ; 読み書き可能セグメント32bit DW 0xffff,0x0000,0x9a28,0x0047 ; 実行可能セグメント32bit (bootpack用) DW 0 ; GDTR0: ; LGDT命令へのヒント DW 8*3-1 ; 16ビットのリミット(終わり) DD GDT0 ; 32ビットの開始番地 ALIGNB 16 ; 16倍にする bootpack: ; bootpackの始まり!
ALIGNB命令でアドレスを8の倍数にして、暫定的なGDTを作ってる。GTR0はLGDT命令に対する指示
要は、32ビットプロテクトモードに移行する準備をして、移行したあとにデータを対応させている感じになります
感想
最後は伏兵でしたが、何とか切り抜けました。マウスが動いたのはほんと感動です。