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

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

Chapter 23 「グラフィックいろいろ」

今回やった内容

  • malloc
  • グラフィックいろいろ
  • キー入力

環境

作業記録

malloc

 アプリ用のmallocを作ります。
 OS用のMEMMANで確保されたメモリにアプリからアクセスすることは出来ないので、アプリのデータセグメントを別のMEMMANで管理するようにします。

 本では BIN2HRB を使ってmalloc用の領域を指定する…とありますが、今の状態では使えないので har.ld を変更して少し多めにアプリのセグメントを準備するようにしておきます。

/* api.c */

if(edx == 8){      // memman初期化
    memman_init((struct MEMMAN *) (ebx + ds_base));
    ecx &= 0xfffffff0;
    memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
    return 0;
}

if(edx == 9){      // malloc
    ecx = (ecx + 0x0f) & 0xfffffff0;
    reg[7] = memman_alloc((struct MEMMAN *) (ebx + ds_base), ecx);
    return 0;
}

if(edx == 10){     // mfree
    ecx = (ecx + 0x0f) & 0xfffffff0;
    memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
    return 0;
}
/* api_link_obj.asm */

api_init_memman:     ; api_init_memnan()
    PUSH     EBX
    MOV      EDX, 8
    MOV      EBX, [CS:0x0020]
    MOV      EAX, EBX
    ADD      EAX, 32*1204
    MOV      ECX, [CS:0x0000]
    SUB      ECX, EAX
    INT  0x40
    POP  EBX
    RET

api_malloc:              ; api_malloc(int size)
    PUSH EBX
    MOV      EDX, 9
    MOV      EBX, [CS:0x0020]
    MOV      ECX, [ESP + 8]
    INT  0x40
    POP      EBX
    RET

api_mfree:               ; api_mfree(char *addr, int size)
    PUSH EBX
    MOV      EDX, 10
    MOV      EBX, [CS:0x0020]
    MOV      EAX, [ESP + 8]
    MOV      ECX, [ESP + 12]
    INT  0x40
    POP      EBX
    RET

 初期化する際にEAXに 32 * 1024 を足していますが、これはMEMMAN構造体用です。MEMMAN構造体を使うときにアドレスしか渡していませんが、こうすると指定したアドレス以降の必要な領域を自動で計算して使ってくれます。(最初ちょっと混乱しましたが…)

 これでアプリ用のmallocが完成しました!

グラフィックいろいろ

 これからとにかく実装するだけです。OSに既にある関数をAPIを通してアプリから呼び出せるようにします。

点を描画する

 ESIにX座標、EDIにY座標、EAXに色番号を指定します。 

/* api.c */

if(edx == 11){      // ウィンドウに点を描画する
    struct SHEET *sheet = (struct SHEET *) (ebx & 0xfffffffe);
    sheet->buf[sheet->buf_width * edi + esi] = eax;
    return 0;
}

ウィンドウリフレッシュ

 EAX/ECX/ESI/EDI に x0/y0/x1/y1 を指定します。

/* api.c */

if(edx == 12){     // ウィンドウリフレッシュ
    struct SHEET *sheet = (struct SHEET *) ebx;
    sheet_refresh(sheet, eax, ecx, esi, edi);
    return 0;
}

 リフレッシュ用APIが完成したので、既に実装済みのいくつかのグラフィックAPIを変更します。どのような変更かというと、EBXのSHEETアドレスが奇数ならリフレッシュを自動で行わないようにします。SHEETアドレスは必ず偶数になるのでその性質を使います。0xfffffffe とANDを取ることで下位1ビットのみを取り出し、そのビットが1かどうかで偶奇を判定しています。

struct SHEET *sheet = (struct SHEET *) (ebx & 0xfffffffe);

if(ebx & 0xfffffffe == 0){
    sheet_refresh(sheet, eax, ecx, esi + 1, edi + 1);
}

線を描画する

 線を描画する関数は新しく作ります。x0/y0/x1/y1 から傾きを求めて、線を描画するようになっています。傾きを求める際、小さな数を擬似的に扱うため最初にXとYの値を 1024 倍しています。使うときは 10 ビット右にシフトすることで 1024 で割って元の数に戻しています。単純に 1000 倍するのではなくシフト演算を使うことで描画スピードを上げています。

/* graphic.c */

void drawline(struct SHEET *sheet, int x0, int y0, int x1, int y1, int color){
    int dx = x1 - x0;
    int dy = y1 - y0;
    int x = x0 << 10;
    int y = y0 << 10;
    int len = 0;

    if(dx < 0) dx = -dx;
    if(dy < 0) dy = -dy;

    // 変化量設定
    // (2 * (bool) - 1) => boolがtrueなら1, falseなら-1を返す
    if(dy <= dx){
        len = dx + 1;
        dx = 1024 * (2 * (x0 < x1) - 1);
        dy = (y1 - y0 + (2 * (y0 <= y1) - 1) << 10) / len;
    }else{
        len = dy + 1;
        dy = 1024 * (2 * (x0 < x1) - 1);
        dx = (x1 - x0 + (2 * (x0 <= x1) - 1) << 10) / len;
    }

    for(int cnt = 0; cnt < len; ++ cnt){
        sheet->buf[sheet->buf_width * (y >> 10) + (x >> 10)] = color;
        x += dx;
        y += dy;
    }
}

ウィンドウを閉じる

 sheet_freeを呼びます!!楽!!

/* api.c */

if(edx == 14){      // ウィンドウを閉じる
     sheet_free((struct SHEET *) ebx);
    return 0;
}

 ちゃんと動きます!綺麗…

 グラフィック関連APIはとにかく実装…という感じなので疾走感があります。

キー入力

 アプリがキー入力を受け取れるようにします。実装は特に難しくなく、FIFOバッファをチェックしてデータが存在するなら返す…とするだけです。オプションでキー入力がされるまで待つか、1回だけFIFOバッファを確認してすぐ結果を返すかを選べるようにしています。もしキー入力までずっと待機する場合は、カーソルタイマーなどの処理を間時間に行うようにします。

/* api.c */

if(edx == 15){      // キー入力
    while(1){
        // FIFO確認
        io_cli();
        if(fifo32_status(&task->fifo) == 0){
            if(eax != 0){
                task_sleep(task);
            }else{
                io_sti();
                reg[7] = -1;
                return 0;
            }
        }

        // キー入力を待っている間は他のことをしたり…
        int data = fifo32_get(&task->fifo);
        io_sti();
        if(data <= 1){  // タイマー
            timer_init(console->cursor_timer, &task->fifo, 1);
            timer_set(console->cursor_timer, 50);
        }else if(data == 2){    // カーソルON
            console->cursor_color = COL8_FFFFFF;
        }else if(data == 3){    // カーソルOFF
            console->cursor_color = -1;
        }else if(256 <= data && data <= 512){   // キー入力だった
            reg[7] = data - 256;
            return 0;
        }
    }
}

 前回の方法と同じように、スタックの値を書き換えることによって値を返しています。


 …ということでアプリでキー入力を受け取れるようになったので簡単なゲーム(?)らしきものを作りました。23日目完成です!

 (動画内でちょっとだけ出ていますが、強制終了したときにもウィンドウが閉じられるようになっています)

まとめ

 グラフィック関連のAPIが充実してきました。キー入力も受け取れるになったので簡単なゲームならもう作れるようになった…ような気がします。多分作れます。
 自作OS本もあと1週間になりました。終わったらどんな風に改造していくか…楽しみです。