30日でできる!OS自作入門4日目
何をやるか
- アセンブラみたいにC言語でメモリーに書きこみたい。そこでC言語のポインタを用いてメモリーを書きかえる。
- VRAMを書き換えると画面の色を変えることができるので、ポインタを用いて書き換える。
- 色番号の設定してGUIを描く。
画面の表示入門
VRAMがある0xa0000~0xaffff番地を p=(char *)i;
*p=i & 0x0f;
と書いて換える。i番地に0x0fをandすると00,01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,00,...
という16画素ごとに色番号を繰りかえす。そのため320×200の画面に繰り返された値が代入されて縞々模様が表示される。
void io_hlt(void); void HariMain(void) { int i; char *p; for(i=0xa0000; i<=0xaffff; i++){ p=(char *)i; *p=i & 0x0f; } for (;;) { io_hlt(); } }
実際に出力された縞々模様の画面。見ていると目が痛くなりそう。
また p=(char *)i; *p=i & 0x0f;
をアセンブラで説明すると、以下のようにECXレジスタにメモリーの番地を代入して、ECX番地に(i & 0x0f)
を代入するという説明は大変分かりやすかった。
MOV ECX i MOV BYTE[ECX],(i & 0x0f)
色番号の設定
現在は320×200の8ビットカラーモードで画面を表示しているため、扱える色は乏しくOSのGUIを描くのは難しい。そのため、色番号を設定し直す必要がある。0~15の色番号を黒、明るい赤、明るい黄色などの16色を設定する。 色番号の設定のためにビデオDAコンバーターのパレットにアクセスして書き換える必要がある。色はカラーコードのサイトが参考になる。- パレットにアクセスして書き換える手順
割込みフラグは32ビットレジスタのEFLAGSの9ビット目にあるので状態をチェックするのは困難である。実装ではCLI命令する前にEFLAGSの値を保存して、色の設定番号を書き換え後にEFLAGSレジスタに保存した値を代入する方法をとる。多分CLI命令する前の状態で割込みフラグが0の場合を考慮して、STI命令ではなくこの方法をとるのだろう。
C言語に書くと以下になるが、なぜio_out8(0x03c9, rgb[0] / 4);
でrgbを4で割る理由がわからない。
void init_palette(void) { static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, /* 0:黒 */ 0xff, 0x00, 0x00, /* 1:明るい赤 */ 0x00, 0xff, 0x00, /* 2:明るい緑 */ 0xff, 0xff, 0x00, /* 3:明るい黄色 */ 0x00, 0x00, 0xff, /* 4:明るい青 */ 0xff, 0x00, 0xff, /* 5:明るい紫 */ 0x00, 0xff, 0xff, /* 6:明るい水色 */ 0xff, 0xff, 0xff, /* 7:白 */ 0xc6, 0xc6, 0xc6, /* 8:明るい灰色 */ 0x84, 0x00, 0x00, /* 9:暗い赤 */ 0x00, 0x84, 0x00, /* 10:暗い緑 */ 0x84, 0x84, 0x00, /* 11:暗い黄色 */ 0x00, 0x00, 0x84, /* 12:暗い青 */ 0x84, 0x00, 0x84, /* 13:暗い紫 */ 0x00, 0x84, 0x84, /* 14:暗い水色 */ 0x84, 0x84, 0x84 /* 15:暗い灰色 */ }; set_palette(0, 15, table_rgb); return; /* static char 命令は、データにしか使えないけどDB命令相当 */ } void set_palette(int start, int end, unsigned char *rgb) { int i, eflags; eflags = io_load_eflags(); /* 割り込み許可フラグの値を記録する */ io_cli(); /* 許可フラグを0にして割り込み禁止にする */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); //4を割る理由はなんなんだろうか io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 割り込み許可フラグを元に戻す */ return; }
関数io_cli(); io_out8(); io_store_eflags(eflags)
は、Cでは扱えない命令があるためアセンブラ言語で定義する必要がある。以下がコードである。
_io_cli: ; void io_cli(void); CLI RET _io_out8: ; void io_out8(int port, int data); MOV EDX,[ESP+4] ; port MOV AL,[ESP+8] ; data OUT DX,AL RET _io_load_eflags: ; int io_load_eflags(void); PUSHFD ; PUSH EFLAGS という意味 POP EAX RET _io_store_eflags: ; void io_store_eflags(int eflags); MOV EAX,[ESP+4] PUSH EAX POPFD ; POP EFLAGS という意味 RET
コードから以下のことが分かった。
- io_out8関数の引数の値がメモリーのESP番地以降に格納されている。引数の値はint型の32ビットなので一つの引数に4番地使われる。恐らく
MOV ESX,[ESP+4]
されるとESXは上位ビットから[ESP+7],[ESP+6],[ESP+5],[ESP+4]と値が代入されるのだろう。 - io_load_eflagsはMOV EAX EFLAGSが仕様でできないので、EFLAGSをスタックにプッシュしてポップした値をEAXに格納する受け渡しになる。return文であるRET命令で関数が終了した時関数のEAXは返り値になる。
- io_store_eflagsはMOV EFLAGS EAXができないのでio_load_eflagsと同様にスタックを介してデータの受け渡しを行う。
0~15の設定番号を変えた場合の縞々模様の画面
4日目では最終的にこういうGUIを描いた。
感想
色番号を設定し直すところで割込みやEFLAGSレジスタなどの新しい話が多くて、少しややこしかった。まとめるのに時間がかかった。一日一日をもうちょっとコンパクトにまとめた方がいいように思える。