30日OS自作入門-5日目(GDTとIDTの前まで)-
だいたい投稿の間隔は1週間という感じでしょうか。
もしかしたらもうちょっとかかるかもしれませんけれども。
ということで5日目のラスト以外の内容について。
今回のまとめ
主には、4日目に表示させた画面上に文字だったり、マウスカーソルを表示させていきます。
どん詰まりすると思われるのはやはりフォントを増やす部分(harib02e)と変数の値を画面に表示させる(harib02g)部分かと思います。そこについては参考となるサイトをガン見してやっていけば問題ないかと思います。
harib02a
nasmhead.nasで記録されている画面モードを取得して、それを使って処理をおこなっていきます。
まずは、bootpack.cを変更していきます。
上記の処理と、4日目の最後harib01hで表示したboxfill8が大量に並んでいるところを関数としてまとめて定義(init_screen関数)します。
/* proto type declaration */ extern void io_hlt(void); extern void io_cli(void); extern void io_out8(int port, int data); extern int io_load_eflags(void); extern void io_store_eflags(int eflags); void init_palette(void); void set_palette(int start, int end, unsigned char *rgb); void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1); void init_screen(char *vram, int x, int y); #define COL8_000000 0 #define COL8_FF0000 1 #define COL8_00FF00 2 #define COL8_FFFF00 3 #define COL8_0000FF 4 #define COL8_FF00FF 5 #define COL8_00FFFF 6 #define COL8_FFFFFF 7 #define COL8_C6C6C6 8 #define COL8_840000 9 #define COL8_008400 10 #define COL8_848400 11 #define COL8_000084 12 #define COL8_840084 13 #define COL8_008484 14 #define COL8_848484 15 void HariMain(void) { char *vram; int xsize, ysize; short *binfo_scrnx, *binfo_scrny; int *binfo_vram; init_palette(); /* setting palette */ binfo_scrnx = (short *) 0x0ff4; binfo_scrny = (short *) 0x0ff6; binfo_vram = (int *) 0x0ff8; xsize = *binfo_scrnx; ysize = *binfo_scrny; vram = (char *) *binfo_vram; init_screen(vram, xsize, ysize); for(;;){ io_hlt(); } } void init_palette(void) { static unsigned char table_rgb[16*3] = { 0x00, 0x00, 0x00, /* 0:black */ 0xff, 0x00, 0x00, /* 1:light red */ 0x00, 0xff, 0x00, /* 2:light green */ 0xff, 0xff, 0x00, /* 3:light yellow */ 0x00, 0x00, 0xff, /* 4:light blue */ 0xff, 0x00, 0xff, /* 5:light purple */ 0x00, 0xff, 0xff, /* 6:light water blue */ 0xff, 0xff, 0xff, /* 7:white */ 0xc6, 0xc6, 0xc6, /* 8:light gray */ 0x84, 0x00, 0x00, /* 9:dark red */ 0x00, 0x84, 0x00, /*10:dark green */ 0x84, 0x84, 0x00, /*11:dark yellow */ 0x00, 0x00, 0x84, /*12:dark blue */ 0x84, 0x00, 0x84, /*13:dark purple */ 0x00, 0x84, 0x84, /*14:dark water blue */ 0x84, 0x84, 0x84 /*15:dark gray */ }; set_palette(0, 15, table_rgb); return; /* static char operation is equals to DB operation */ } void set_palette(int start, int end, unsigned char *rgb) { int i, eflags; eflags = io_load_eflags(); /* record the allowing interrupt flags value */ io_cli(); /* set allowing flag 0 for prohibitting interrupt */ io_out8(0x03c8, start); for(i = start; i <= end; i++){ io_out8(0x03c9, rgb[0]/4); io_out8(0x03c9, rgb[1]/4); io_out8(0x03c9, rgb[2]/4); rgb += 3; /* gain rgb pointer index 3 */ } io_store_eflags(eflags); /* restore interrupt allowing flags */ return; } 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; } void init_screen(char *vram, int x, int y) { boxfill8(vram, x, COL8_008484, 0, 0, x - 1, y - 29); boxfill8(vram, x, COL8_C6C6C6, 0, y - 28, x - 1, y - 28); boxfill8(vram, x, COL8_FFFFFF, 0, y - 27, x - 1, y - 27); boxfill8(vram, x, COL8_C6C6C6, 0, y - 26, x - 1, y - 1); boxfill8(vram, x, COL8_FFFFFF, 3, y - 24, 59, y - 24); boxfill8(vram, x, COL8_FFFFFF, 2, y - 24, 2, y - 4); boxfill8(vram, x, COL8_848484, 3, y - 4, 59, y - 4); boxfill8(vram, x, COL8_848484, 59, y - 4, 59, y - 5); boxfill8(vram, x, COL8_000000, 2, y - 3, 59, y - 3); boxfill8(vram, x, COL8_000000, 60, y - 24, 60, y - 3); boxfill8(vram, x, COL8_848484, x - 47, y - 24, x - 4, y - 24); boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y - 4); boxfill8(vram, x, COL8_FFFFFF, x - 47, y - 3, x - 4, y - 3); boxfill8(vram, x, COL8_FFFFFF, x - 3, y - 24, x - 3, y - 3); return; }
今回は、コードをまとめたりしただけなので、実際にmakeして実行させてもharib01hのときのものと結果は変わりません。
HariMainのところで追加で変数宣言したり、値を代入を以下のようにしています。
void HariMain(void) { char *vram; int xsize, ysize; short *binfo_scrnx, *binfo_scrny; int *binfo_vram; init_palette(); /* setting palette */ binfo_scrnx = (short *) 0x0ff4; binfo_scrny = (short *) 0x0ff6; binfo_vram = (int *) 0x0ff8; xsize = *binfo_scrnx; ysize = *binfo_scrny; vram = (char *) *binfo_vram;
で、binfo_scrnxやbinfo_scrny, binfo_vramにアドレスを格納していますが、これらはnasmhead.nasで画面解像度の値が格納されているアドレスになります。
nasmhead.nasで該当する部分がこちら。
; related to BOOT_INFO CYLS EQU 0x0ff0 ; setting boot sector LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; how many bit color ? SCRNX EQU 0x0ff4 ; X display resolution SCRNY EQU 0x0ff6 ; Y display resolution VRAM EQU 0x0ff8 ; start address fo graphic buffer ORG 0xc200 ; start address this program is loaded ; display mode settings MOV AL,0x13 ; VGA graphics, 320x200x8bit color MOV AH,0x00 ; fixed value INT 0x10 MOV BYTE [VMODE],8 ; store display mode in the following MOV WORD [SCRNX],320 MOV WORD [SCRNY],200 MOV DWORD [VRAM],0x000a0000
SCRNX, SCRNY, VRAMのアドレスに値をそれぞれ320, 200, 0xa0000と入れています。この値をbinfo_ふんちゃかでは参照しています。
また、harib01hの時にHariMain関数にあった大量のboxfill8関数をまとめてinit_screen関数に置いたことでHariMainの中身がすっきりしました。
この後のharib02bとharib02cは構造体とアロー演算子の話なのですっとばします。
アロー演算子は、構造体のポインタ変数を宣言した際に、そのメンバのポインタを指定する際に使用するものです。
通常の構造体変数を宣言した場合はメンバへのアクセスはドットを使い、構造体ポインタを宣言した場合にはメンバへのアクセスはアロー演算子を使う、といった具合。
harib02d
今まで何もなかった画面上にAという文字を表示させます。
変更するのはbootpack.cになります。コードは以下の通り。
/* proto type declaration */ extern void io_hlt(void); extern void io_cli(void); extern void io_out8(int port, int data); extern int io_load_eflags(void); extern void io_store_eflags(int eflags); void init_palette(void); void set_palette(int start, int end, unsigned char *rgb); void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1); void init_screen(char *vram, int x, int y); void putfont8(char *vram, int xsize, int x, int y, char c, char *font); #define COL8_000000 0 #define COL8_FF0000 1 #define COL8_00FF00 2 #define COL8_FFFF00 3 #define COL8_0000FF 4 #define COL8_FF00FF 5 #define COL8_00FFFF 6 #define COL8_FFFFFF 7 #define COL8_C6C6C6 8 #define COL8_840000 9 #define COL8_008400 10 #define COL8_848400 11 #define COL8_000084 12 #define COL8_840084 13 #define COL8_008484 14 #define COL8_848484 15 struct BOOTINFO{ char cyls, leds, vmode, reserve; short scrnx, scrny; char *vram; }; void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; static char font_A[16] = { 0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24, 0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00 }; init_palette(); /* setting palette */ init_screen(binfo->vram, binfo->scrnx, binfo->scrny); putfont8(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_A); for(;;){ io_hlt(); } } void init_palette(void) { static unsigned char table_rgb[16*3] = { 0x00, 0x00, 0x00, /* 0:black */ 0xff, 0x00, 0x00, /* 1:light red */ 0x00, 0xff, 0x00, /* 2:light green */ 0xff, 0xff, 0x00, /* 3:light yellow */ 0x00, 0x00, 0xff, /* 4:light blue */ 0xff, 0x00, 0xff, /* 5:light purple */ 0x00, 0xff, 0xff, /* 6:light water blue */ 0xff, 0xff, 0xff, /* 7:white */ 0xc6, 0xc6, 0xc6, /* 8:light gray */ 0x84, 0x00, 0x00, /* 9:dark red */ 0x00, 0x84, 0x00, /*10:dark green */ 0x84, 0x84, 0x00, /*11:dark yellow */ 0x00, 0x00, 0x84, /*12:dark blue */ 0x84, 0x00, 0x84, /*13:dark purple */ 0x00, 0x84, 0x84, /*14:dark water blue */ 0x84, 0x84, 0x84 /*15:dark gray */ }; set_palette(0, 15, table_rgb); return; /* static char operation is equals to DB operation */ } void set_palette(int start, int end, unsigned char *rgb) { int i, eflags; eflags = io_load_eflags(); /* record the allowing interrupt flags value */ io_cli(); /* set allowing flag 0 for prohibitting interrupt */ io_out8(0x03c8, start); for(i = start; i <= end; i++){ io_out8(0x03c9, rgb[0]/4); io_out8(0x03c9, rgb[1]/4); io_out8(0x03c9, rgb[2]/4); rgb += 3; /* gain rgb pointer index 3 */ } io_store_eflags(eflags); /* restore interrupt allowing flags */ return; } 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; } void init_screen(char *vram, int x, int y) { boxfill8(vram, x, COL8_008484, 0, 0, x - 1, y - 29); boxfill8(vram, x, COL8_C6C6C6, 0, y - 28, x - 1, y - 28); boxfill8(vram, x, COL8_FFFFFF, 0, y - 27, x - 1, y - 27); boxfill8(vram, x, COL8_C6C6C6, 0, y - 26, x - 1, y - 1); boxfill8(vram, x, COL8_FFFFFF, 3, y - 24, 59, y - 24); boxfill8(vram, x, COL8_FFFFFF, 2, y - 24, 2, y - 4); boxfill8(vram, x, COL8_848484, 3, y - 4, 59, y - 4); boxfill8(vram, x, COL8_848484, 59, y - 4, 59, y - 5); boxfill8(vram, x, COL8_000000, 2, y - 3, 59, y - 3); boxfill8(vram, x, COL8_000000, 60, y - 24, 60, y - 3); boxfill8(vram, x, COL8_848484, x - 47, y - 24, x - 4, y - 24); boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y - 4); boxfill8(vram, x, COL8_FFFFFF, x - 47, y - 3, x - 4, y - 3); boxfill8(vram, x, COL8_FFFFFF, x - 3, y - 24, x - 3, y - 3); return; } void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { int i; char *p, d; // d == data for(i = 0; i < 16; i++){ p = vram + (y + i) * xsize + x; d = font[i]; if((d & 0x80) != 0){ p[0] = c; } if((d & 0x40) != 0){ p[1] = c; } if((d & 0x20) != 0){ p[2] = c; } if((d & 0x10) != 0){ p[3] = c; } if((d & 0x08) != 0){ p[4] = c; } if((d & 0x04) != 0){ p[5] = c; } if((d & 0x02) != 0){ p[6] = c; } if((d & 0x01) != 0){ p[7] = c; } } return; }
まず、構造体ポインタbinfoはアドレス0x0ff00をさしています。構造体自体は配列と同じように、メモリの連続したアドレス上にデータを格納します。配列と異なる点は、構造体の場合メンバ変数ごとに型が異なることです(メンバ変数がすべて同じ型であれば配列と同じようになると思います)。
それを踏まえると、構造体のメンバのアドレスはそれぞれ以下の通りになります。
0x0ff0 : cyls, 0x0ff1 : leds, 0x0ff2 : vmode, 0x0ff3 : reserve, 0x0ff4 : scrnx, 0x0ff6 : scrny, 0x0ff8 : vram char型は1バイトを使用、short型は2バイトを使用
それぞれのアドレスがnasmhead.nasで定義したBOOT_INFOの部分に一致するのが確認できるかと思います(上に記載したnasmhead.nasの該当部分を参照してください)。
次にfont_A配列について。
これは本に書いてある通り、「A」という文字を8(横)×16(縦)のドットで表現した場合に「塗りつぶしたドット部分」をhexで表した値を配列として定義したものになります。
下に表したものだとわかりやすいかな。
0 0 0 0 0 0 0 0 = 0x00 0 0 0 1 1 0 0 0 = 0x18 0 0 0 1 1 0 0 0 = 0x18 0 0 0 1 1 0 0 0 = 0x18 0 0 0 1 1 0 0 0 = 0x18 0 0 1 0 0 1 0 0 = 0x24 0 0 1 0 0 1 0 0 = 0x24 0 0 1 0 0 1 0 0 = 0x24 0 0 1 0 0 1 0 0 = 0x24 0 1 1 1 1 1 1 0 = 0x7e 0 1 0 0 0 0 1 0 = 0x42 0 1 0 0 0 0 1 0 = 0x42 0 1 0 0 0 0 1 0 = 0x42 1 1 1 0 0 1 1 1 = 0xe7 0 0 0 0 0 0 0 0 = 0x00 0 0 0 0 0 0 0 0 = 0x00
2進数表現でドットを塗りつぶした部分を1とした場合、各行の2進数をhexで表したものがfont_A配列に格納されています。
あとは、これをvramに書き込んであげることで「A」という文字を表示します。その処理を行うのがputfont8関数です。
putfont8関数では、上で定義したfont_A配列を使ってAを表示します。
for文内にあるif文は各ループにおいて全て実施されます。記述されているif文は、font_Aの各配列要素を2進数で考えた際に「1になっている部分を識別して、その部分に色をセットする」ことをやっています。どの色をセットするかはputfont8のarg4で指定したもの(今回の場合はCOL8_FFFFFF)で決定しています。
ということで、実行した結果は以下のようになります。
図形だけでなく文字もちゃんと表示することができました。
harib02e
フォントを増やして色々な文字を表示する、という話なのですがここでも独自ツール。。。はぁ。
フォントの元となるhankaku.txtファイルはあるので、それを読み取って先ほどのAと同じようなものを作るコードが必要になります。
処理の方針としては、hankaku.txtに書かれているドットとアスタリスクで描かれたものを読み取り、ドットだったら0、アスタリスクだったら1に変換して、それをfont_X[16]といった形でhexの値を入れていくという形です。
で、自分にはコードを自力で書く能力はないので、いつも通り参考となるサイトに書いてあるものを見よう見まねでやってみます。
参考とするのは、以下のサイトです。いつも参考にさせていただいている「サラリーマンがハッカーを真剣に目指す」のOS自作5日目です
GDT(グローバルディスクリプタテーブル) | OS自作入門 5日目-1 【Linux】 | サラリーマンがハッカーを真剣に目指す
ということで、上記に書いてあるコードを参考に(丸パクリ)します。
converter.cとして保存し、まずはこいつをコンパイルして実行します
(このコードについては、下で自分なりの解説などを入れたいと思います)
gcc converter.c -o converter.o
./converter.o
実行がうまくいくと、ディレクトリにhankaku.cというコードが出てきますので、これをつかってOS上でA以外のフォントを表示させます。
bootpack.cでの変更点は、まずfont_Aの配列をなくしてhankaku.cで定義している配列を取り込むコードをかきます。次に取り込んだ配列を使って複数の文字を画面上に描画する処理をかきます。
今回は変更点だけ以下に記します(他の部分は変更ありません)。
void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; extern char hankaku[4096]; init_palette(); /* setting palette */ init_screen(binfo->vram, binfo->scrnx, binfo->scrny); putfont8(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, hankaku + 'A' * 16); putfont8(binfo->vram, binfo->scrnx, 16, 8, COL8_FFFFFF, hankaku + 'B' * 16); putfont8(binfo->vram, binfo->scrnx, 24, 8, COL8_FFFFFF, hankaku + 'C' * 16); putfont8(binfo->vram, binfo->scrnx, 40, 8, COL8_FFFFFF, hankaku + '1' * 16); putfont8(binfo->vram, binfo->scrnx, 48, 8, COL8_FFFFFF, hankaku + '2' * 16); putfont8(binfo->vram, binfo->scrnx, 56, 8, COL8_FFFFFF, hankaku + '3' * 16);
また、hariboteos.img作成時にもhankaku.cをリンクさせないといけないので、Makefileを少し変更します。
とはいってもbootpack.hrbの部分にhankaku.cを追加するだけです。
ootpack.hrb: hankaku.c bootpack.c nasmfunc.o os.lds gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c bootpack.c -o bootpack.hrb
実行した結果は以下の通りです。
いいかんじです!
harib02f
文字列をかきますが、やることは先ほどのコードのうち、文字を設定する関数をまとめるだけです。で、文字列の終端はヌルバイトになるため、ヌルバイトに到達したら設定を終了するという処理をかけばいいです。bootpack.cに関数を追加しますが、その関数がこちら。
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s) { extern char hankaku[4096]; for (; *s != 0x00; s++) { putfont8(vram, xsize, x, y, c, hankaku + *s * 16); x += 8; } return; }
実行した結果は以下の通りです。
harib02g
変数の値を表示させます。
が、他のサイトでもあるとおり、sprintf関数をそのまま使おうとするとエラーが出て先に進めなくなります。
私の場合、以下のようなエラーが出ました。
forensic@forensic-virtual-machine:~/tegaki_os/day5/harib02g$ make hariboteos.img nasm ipl.nas -o ipl.bin nasm nasmhead.nas -o nasmhead.bin -l nasmhead.lst nasm -f elf nasmfunc.nas -o nasmfunc.o -l nasmfunc.lst gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c bootpack.c -o bootpack.hrb /tmp/ccfzvo0m.o: In function `HariMain': bootpack.c:(.text+0xc3): undefined reference to `sprintf' collect2: error: ld returned 1 exit status Makefile:2: recipe for target 'bootpack.hrb' failed make: *** [bootpack.hrb] Error 1
リンクで問題がおこっているっぽいので、その辺どうにか対処すればいきそうなのですがちょっと今回はさくっといきたいのでharib02eの時と同様に参考サイトのsprintf関数のコードをお借りします。
sprintfを実装する | OS自作入門 5日目-2 【Linux】 | サラリーマンがハッカーを真剣に目指す
上記のリンクにあるコードをmysprintf.cとして保存します。
あとは、bootpack.cでmysprintf.cで定義されている関数を使うので、Makefileを編集してちゃんとリンクされるように設定します。
変更したのはbootpack.hrbを生成する部分で、加えてmysprintf.oを生成する項目を新たに記述いたしました。
bootpack.hrb: hankaku.c mysprintf.o bootpack.c nasmfunc.o os.lds gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c mysprintf.o bootpack.c -o bootpack.hrb ・・・ mysprintf.o: mysprintf.c gcc -c -m32 -march=i486 -nostdlib mysprintf.c -o mysprintf.o
bootpack.cのコードについては本の内容で十分かと思いますので、改めてここで説明する必要はないかと。
ということで、実行した結果は以下の通りです。