30日でできるOS自作入門_DAY4

harib01a

画面に出力を行うために、 naskfunc.nasに以下の関数が追加された メモリ番地を指定して書き込むのが目的とのこと

_write_mem8:    ; void write_mem8(int addr, int data);
        MOV     ECX,[ESP+4]     ; [ESP+4]にaddrが入っているのでそれをECXに読み込む
        MOV     AL,[ESP+8]      ; [ESP+8]にdataが入っているのでそれをALに読み込む
        MOV     [ECX],AL
        RET

C言語(bootpack.c)側では、以下で呼び出している

for (i = 0xa0000; i <= 0xaffff; i++) {
        write_mem8(i, 15); /* MOV BYTE [i],15 */
    }*/

どうやら、C言語からnaskの関数を呼び出す時の引数の仕様として、以下のようになるっぽい。

  • 関数の読み込まれるメモリアドレス:[ESP] ※ESPレジスタに関数の格納された ‘アドレスが’ 格納される。アセンブラで ‘’ をつけることで、そのアドレスに格納された値を操作可能
  • 第1引数の格納場所:[ESP + 4]
  • 第1引数の格納場所:[ESP + 8]
  • 第1引数の格納場所:[ESP + 8]

上記をふまえコメントを書き換えてみる。

_write_mem8:   ; void write_mem8(int addr, int data);
        MOV     ECX,[ESP+4]     ; [ESP+4]にC言語側で関数がコールされた時の第一引数=address(書き込み先アドレス)が入っているので、その値をECXレジスタに格納する
        MOV     AL,[ESP+8]      ; [ESP+8]にC言語側で関数がコールされた時の第2引数=data(書き込むデータ)が入っているので、その値をALレジスタに格納する
        MOV     [ECX],AL        ; [ESP+4]で指定されたアドレスにdataの値を書き込む
        RET

結果的に、0xa0000 から 0xaffff までのアドレスにかたっぱしから’15’を書き込む処理となっている。

harib01b

bootpack.cのコードが変わった

void io_hlt(void);
void write_mem8(int addr, int data);


void HariMain(void)
{
    int i; /* 変数宣言。iという変数は、32ビットの整数型 */

    for (i = 0xa0000; i <= 0xaffff; i++) {

-       write_mem8(i, 15);
+       write_mem8(i, i  0x0f);

    }

    for (;;) {
        io_hlt();
    }
}

i % 0x0f だが、はアンド演算子と呼ばれるらしい。

OR演算(どちらかが1だったら1にする)

0100 OR 0010 ⇒ 0110
1010 OR 0010 ⇒ 1010

AND演算(両方が1の時のみ1にする)

0100 AMD 1101 ⇒ 0100
1010 AND 1101 ⇒ 1000

最後にXOR演算(片方が0でもう片方が1の時のみ1にする)

0100 XOR 1101 ⇒ 1001
1010 XOR 0010 ⇒ 1000

harib01f

  • char型の変数には、以下の3つのモードがある。
    • signed型 データは-128~127を表す
    • unsigned型 データは0~255を表す
    • 型指定なし コンパイラに判断を委ねる?
  • EFLAGSとは、16bitレジスタのfflagsを拡張したもので、 キャリーフラグや割り込みフラグが詰まっている。
    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命令相当 */
    }

char型の配列 table_rgb[48] を作成し、48個のデータを格納後、 set_palette関数を起動している。

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);
        io_out8(0x03c9, rgb[1] / 4);
        io_out8(0x03c9, rgb[2] / 4);
        rgb += 3;
    }
    io_store_eflags(eflags);    /* 割り込み許可フラグを元に戻す */
    return;
}

set_palette関数では、 割り込み不可状態を作った後で、 0x03c8 及び 0x03c9の装置に対し、out命令を実行する naskの関数を実行している。

io_out8の実装は以下

_io_out8:  ; void io_out8(int port, int data);
        MOV     EDX,[ESP+4]     ; port
        MOV     AL,[ESP+8]      ; data
        OUT     DX,AL
        RET

引数で受け取った装置名とrgb値を4で割った値を それぞれ、EDXレジスタとALレジスタに格納後、 「OUT DX,AL」している。

DXはEDXの下位16bitになるが、なぜここでEDXではなくDXを指定しているのか 理由がわからない。。なんにせよやっていることは変わらずで、 画面に出力する色を変える命令をビデオDAコンバータ?に送っている。

※この手の装置に直接命令を送る関数は言語には存在しないらしい

harib01g

最後に、図形を画面に描画してみる。

現在の画面はX軸が320、Y軸が200の合計64,000の点で構成されている。

上記の座標に何を描画するのかは、 VRAMに格納する値で管理されており(メモリに色番号を入れるとその座標の色が変わる)、 0xa000 + x + y *320 というルールでアドレスが採番される。

/* https://sunrise033.com/entry/hatena-blog-how-to-hierarchicalize-categories */