自作OSに挑戦する日記 5日目

 「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
 この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。

Chapter 5 「構造体と文字列表示とGDT/IDT初期化」

今回やった内容

  • 起動情報を取得する
  • 構造体とメモリの関係について学ぶ
  • 文字列表示をする
  • マウスカーソルを表示する
  • GDT/IDT初期化のコードを書く

環境

作業記録

 4日目に書いたコードでは画面サイズやVRAMの先頭アドレスを直打ちしていましたが、これだと他の画面モードに対応できないので、起動中にメモリに書き込んだデータを読み出します。
 解像度の幅と高さはそれぞれ0x0ff40x0ff6、VRAMの開始番地は0x0ff8 に書き込まれているのでそれを読み出します。

// メモリから画面情報を読み込む

int width = *((short *) 0x0ff4);
int height = *((short *) 0x0ff6);
char *vram = (char *) 0x0ff8;

 ただ、この書き方だと少し見づらいので構造体を使います。起動時にメモリに書き込んだデータは0x0ff0番地から順に配置したので、構造体のポインタに0x0ff0を渡すだけで初期化できます(すごい楽…)。

// 起動情報を受け取るのに構造体を使う

struct BOOTIFNO{
    char cyls, leds, vmode, reserve;
    short width, height;
    char *vram;
}

int HariMain(void){
    struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
    int width = *(binfo).width;
    int height = *(binfo).height;

 〜略〜
}

 次は文字の描画です。文字の描画では、点を打つところを指定しておいてその通りに描画するという、原始的な方法を使うみたいです。本に書かれている図が分かり易かったです。
 8x16でフォントを作っていくのですが、全てを01で表現すると大変なので1列8ビットを16進数で表すという方法でやるみたいです。

// 「A」
static char font_A[16] = {
    0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
    0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
}

 このデータを受け取ることで指定座標に文字を描画してくれるような関数を実装します。

// 1文字描画

void putfont8(unsigned char *vram, int width, int x, int y, unsigned char color, char *font){
    char *p, line_data;

    // 各列上位ビットから見ていって1だったら対応座標に点を打つ
    for(int line = 0; line < 16; line++){
        p =  vram + (y + line) * width + x;
        line_data = font[line];

        for(int bit = 0; bit < 8; bit++){
            if((line_data & (0x80 >> bit)) != 0){
                p[bit] = color;
            }
        }
    }
}

 本に書かれてあったコードはif文が8つ並べられていたのですが、やっていることが単純だったのと、ifを並べるのはあまり好きでないのでシフト演算を使ったコードに書き換えました。

 これを実行すると…

ちゃんと「A」が表示されました!!やはり、自分で1から実装したものが動いているのを見ると感動します…。文字が出てくるとさらにOSっぽさが増しますね。普段当たり前だと思っていた文字列描画が神様みたいに思えてきます。


 Aだけじゃなくて他の文字も使いたいので、フォントを増やすということに挑戦します。ただ、Aの時のように全て手打ちでやると時間がいくらあっても足りません。ということで、フォントファイルを使います。
 フォントファイルの中は以下のようになっています。

char 0xde
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****
....****

 「.」を0、「*」を1として各列8ビットを16進数で表すことで、先ほどのAと同じようにフォントを表すことができます。
 このフォントファイルの形式ではOSが読み込むことができないので、オブジェクトファイルにしてOSにリンクさせてあげる必要があります。本では筆者の方が作成したツールを使用するという説明がありますが、macには対応していないので自作しました。

 自作したプログラムは、フォントファイルを読み込んで各列「.」を0、「*」を1に置換して16進数に変換し、それを他ファイルに書き出すという処理をするようになってます。出力ファイルは.c形式となっています。

// makefont.py

font_file = open("hankaku.txt", "r", encoding="utf-8")
font_lines = font_file.readlines()

c_file = open("hankaku.c", "w", encoding="utf-8")
c_file.write("char hankaku[4096] = {\n\n")

font_data = "\t"
for num, line in enumerate(font_lines):
    if num % 18 == 0:   # フォントのタイトル
        continue

    elif 1 <= num % 18 <= 16: # フォント本体(全部で16行)
        _01body = line.replace(".", "0") \
                      .replace("*", "1") \
                      .replace("\n", "")
        font_line_part = "0x" + format(int(_01body, 2), "x").zfill(2) + ", "
        font_data += font_line_part

    else:
        c_file.write(font_data[:-1] + "\n")
        font_data = "\t"

c_file.write("};")
c_file.close()
font_file.close()

 このプログラムによって生成されたhankaku.cファイルを、OSコンパイルするときにリンクしてあげれば、OS側からフォントデータが読み込めるようになります。

// OSからフォントデータを読み込む -> 文字描画

int main(){
    〜略〜

    extern char hankaku[4096];
    putfont8(vram, width, 4, 4, COL8_FFFFFF, hankaku + `A` * 16);

    〜略〜
}
// コンパイル時にリンク

i386-elf-gcc -march=i486 -m32 -nostdlib -T har.ld -o bootpack.hrb -g bootpack.c nasmfunc.o hankaku.c

 これで実行すると先ほどと同じようにAが描画されます。ちゃんとフォントを読み込めているみたいです〜!(また感動)


 このputfont8関数を1文字ずつ呼び出しても文字列の描画はできるのですが、それだと大変なので文字列を描画する関数を作ります。

// 文字列を描画する関数

// 文字列描画
void putstr8(unsigned char *vram, int width, int x, int y, unsigned char color, unsigned char *str){
    extern char hankaku[4096];

    for(; *str != 0x00; str++){
        putfont8(vram, width, x, y, color, hankaku + *str * 16);
        x += 8;
    }
}

 文字列の最初のポインタを引数で受け取り、文字列の終わり(0x00)になるまでポインタを進めながら、putfont8関数を呼んでいるという感じです。これで、文字列の描画が楽になりました!

 ということで早速お決まりの文を描画してみました。

Hello World


 文字列の描画に成功したので、マウスカーソルも描画してみます。マウスカーソルも文字と同じように、描画する点をあらかじめ決めておいて、描画するときにそれを読み込むという方法をとります。

 マウスカーソルはフォントのように複数種類があるわけではないので、他ファイルから読み込むということはせず、直接bootpack.cに書いていきます。マウスカーソルの描画準備をする関数は少し長いので、マウスカーソルの描画データだけを貼っておきます。

// この情報を元にマウスカーソルが描画される

static char cursor[16][16] = {
        "**************..",
        "*OOOOOOOOOOO*...",
        "*OOOOOOOOOO*....",
        "*OOOOOOOOO*.....",
        "*OOOOOOOO*......",
        "*OOOOOOO*.......",
        "*OOOOOOO*.......",
        "*OOOOOOOO*......",
        "*OOOO**OOO*.....",
        "*OOO*..*OOO*....",
        "*OO*....*OOO*...",
        "*O*......*OOO*..",
        "**........*OOO*.",
        "*..........*OOO*",
        "............*OO*",
        ".............***"
};

 このマウスカーソルの描画データを元にしてVRAMに値を書き込んでいくと…

(ここに画像が表示される予定でしたが、アップロードが上手くいかないのでまたいずれ更新します)

ちゃんとマウスカーソルが描画されました〜(動かないですが)!ますますOSっぽくなりました!Welcome to Haribote OSを描画することで、さらにぽくなってますね。


 マウスカーソルを描画することはできたのですが、マウスを触っても少しも動く気配がありません。マウスなどのデバイスからの入力を受け付けるためには、様々な設定がいるそうなのですが、GDTとIDTを知らなければ何も進まないということで、GDTとIDTについて学びます。

 GDTとIDTについて一通り読んだのですが、基本情報のために勉強したことがそのまま出てきたということもあり、かなり理解することができました(やったぜ)。また、割り込みについても書かれていたのですが、これは本に書かれているイラストがとても分かりやすかったです。GDTやIDTについての詳細は6日目に書かれているということなので、楽しみにしたいと思います。

まとめ

 今日の内容で文字列描画とマウスカーソル描画をすることが出来ました。見た目がどんどんOSっぽくなってます!
 明日(6日目)の内容は、OS内部の仕組みについて深く学ぶみたいです。外観を弄るのもやっぱり楽しいですが、自分としては内部の方がもっと興味があります…。ということで今日の内容の予習もしつつ、明日も頑張りたいと思います〜!(最後が雑)