30日OS自作本5日目
5日目は文字とマウスカーソルの表示をやりました。
文字をどう扱うか
8 × 16のピクセルでAを表現することを考えます。 1行を1バイトの
□ □ □ □ □ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ □ ■ ■ □ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ ■ ■ ■ ■ ■ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ □ ■ □ □ □ □ ■ □ ■ ■ ■ □ □ ■ ■ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
//16バイトで1文字を表現。 //※char型=1バイト static char font_A[16] = { 0x00,// 00000000 0x18,// 00011000 0x18,// 00011000 0x18,// 00011000 0x18,// 00011000 0x24,// 00100100 0x24,// 00100100 0x24,// 00100100 0x24,// 00100100 0x7e,// 01111110 0x42,// 01000010 0x42,// 01000010 0x42,// 01000010 0xe7,// 11100111 0x00,// 00000000 0x00 // 00000000 };
もちろん全ての文字に対してC言語でいちいち配列を作ってられないから、DB命令でアルファベットなど主要な文字の群を実行バイナリに直接書き込み、Cからアドレス経由で指定する。
文字を配列のインデックスに利用する
//ASCIIと同じ並びで1文字あたり16バイト格納。16バイト*265文字=4096バイトぶんのcharが必要。 //※char型=1バイト extern char hankaku[4096]; putfont8(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
putfont8の最後の引数hankaku + 'A' * 16
がミソ。文字データを格納している配列hankaku
の先頭アドレスから0x41 * 16 バイト先に16バイト分の'A'
を表す文字のデータが格納されている。配列 + 文字のポイント演算をデータ参照のインデックスに利用しているというのが高等なテクニックに感じた。*1
//定義 void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { //for分の中は一行ごとの処理。i<16からわかる通り、16行分行う。 for (i = 0; i < 16; i++) { d = font[i]; ・・・(略) //VRAMに位置ピクセルごとに書き込み * 8回行う if ((d & 0x80) != 0) { p[0] = c; } if ((d & 0x40) != 0) { p[1] = c; } ・・・(略) if ((d & 0x02) != 0) { p[6] = c; } if ((d & 0x01) != 0) { p[7] = c; } } return; }
マウスカーソルを動かすためにはセグメンテーションと割り込みが必要。
マウスカーソルを動かすにはGDTとIDTの説明が必要らしい。 5日目にしてはちょっと難しくなるよ、と書かれている。
セグメンテーション
- ORG命令で読み込む番地を指定する
- だが競合が起きると大変。
- メモリを切り分けて好きなブロックの最初の番地を0番地にする。
以下の設定が必要
- セグメントのサイズ
- セグメンの開始番地
- セグメントの管理用属性
セグメントでは0~8191番号が使える(8192個のセグメントが使える)
8192個のセグメントが定義できるわけで、だから設定にはその8倍の65536バイト(=64KB)が必要になります
この8倍っての1つのセグメントの設定に必要なサイズだと思うのだけれど、どこから出てきたのだろう?*2
とにかくこの64KBをGTD:global (segment) descriptor tableという。 使い方としては並んだセグメントの設定の先頭と有効設定個数をCPUのGDTRというレジスタに登録すれば完了。
割り込み
- IDTはinterrupt descriptor tableの略
- ポーリングと割り込みの比較。が入っている。コンピュータは監視対象が多すぎるのでポーリングでOSを作るのは難しい。
- IDTでは割り込み番号0~255を設定可能(つまり割り込みは256種類までしか登録出来ない?)
- 番号と関数を対応付けた表のようになっているらしい。
セグメントと割り込みのコード
データ構造は以下のようになる
//GDTの8バイトの中身 struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }; //IDTの8バイトの中身 struct GATE_DESCRIPTOR { short offset_low, selector; char dw_count, access_right; short offset_high; };
GDTとIDTのメモリ割り当てはコード上ではこんな感じ。
void init_gdtidt(void) { struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000; struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;
関係ないけど上のコードをatomで書いたらmarkdownのシンタックスハイライトがバグった。(Markdown上の**強調表現**
とC言語のシンタックスハイライトが衝突してる?)
以降のコードの詳細。(GDTとIDTに実際に値を割り当てて割り込みとセグメンテーションを設定している箇所)は説明が放棄されている?(明日に廻す)ためか中途半端になっているようですのでday6の記事に書きます
思い出
- 昔割り込みハンドラに登録して云々?ということをやった記憶がある。
- ISRとかいうのを使った覚えがある。
わからないこと
hariboteの開発環境では2進数リテラルが使えない?
char test = 0b01100100;
この行をbootpack.cの66行に入れてmake run
すると以下のようなエラーが入る。
bootpack.c:66: invalid suffix on integer constant
うーん以下のプログラムはgccでコンパイルして実行もできるけどhariboteの開発環境のコンパイラは特殊?
#include <stdio.h> int main(void){ char test = 0b01100100; printf("%c\n",test); //dが出力される }
前回失敗したuchanさんの「OS自作入門*3」
起動ディスクの選択画面まではいけたが、その先が真っ黒で特にhelloworldしなかった。 打ち間違いがあったのかな?時間が無くなったので今日はこの辺でおしまい。
あるブログでこれを実験してくれた人がいて、上手くいかなかったみたいなので、家帰ったらちょいと再実験してみよう。
— OS自作 uchan_nos (@uchan_nos) 2018年5月8日
修正していただいたようなので、再度チャレンジしたものの昨日と状況は変わらず、起動ディスク選択画面まで行くが画面が表示されない。 USBを何本か変えてみたり、FATの種類?*4を変えてみたりしたが上手くいかなかった。 ちょっと特殊な環境でやっているため*5何が原因なのかよくわからない。別のPCを使える機会があれば再度試してみようと思う。
感想
大学時代にAVRマイコンを扱う授業の自由課題として8*8のマトリックスLEDに美咲フォントを表示する要素を作った作品を作ろうと試したことがあった。*6*7今回の内容はC言語でピクセル形式のデータをVRAMで扱うためのノウハウが詰まっていた1章であり、件の実験でやりたかったことに近い内容だったように思う。
当時は美咲フォントのデータを上手く扱うことができず、また時間がなかったため諦めたが、事前にこの本を読んでいれば出来ていた気がする。
まぁ件に限らず、技術書を読んで引き出し増やすのは重要だと思う。