無限遠まで突撃中

ネット日記書きの徒然。

「30日でできる!OS自作入門」を今更ながらやってみる - 4日目

さてさて今日もOSやるぞ

3日目は こちら

4日目 - C言語と画面表示の練習

1 - C言語からメモリに書き込みたい

ここはまだアセンブリですね。プログラムをみると以下のようになっています。

_write_mem8:                  ; void write_mem8(int addr, int data);
        MOV    ECX,[ESP+4]    ; [ESP+4]にaddrが入っている
        MOV    AL,[ESP+8]     ; [ESP+8]にdataが入っている
        MOV    [ECX],AL
        RET

コメントをみるとわかると思いますが、 void write_mem8(int addr, int data); のようなC言語の関数と対応しています。ここでやりたいことは、 addr 番地に data を書き込むことです。
ここでC言語の引数とアセンブリの対応ですが、

第一引数 : [ESP+4]
第二引数 : [ESP+8]
第三引数 : [ESP+12]
...

となっております。
現在32ビットモードでプログラムを組んでいるので積極的に32ビットレジスタを使っています。16ビットレジスタを使うこともできるのですが、機械語のバイト数が増え、実行速度も遅くなるらしいです。
C言語と連携する際には、自由に使ってもいいレジスタが制限されていて、 EAX , ECX , EDX の3つだけです。他のレジスタは読み込みはOKですが書き込みはだめなので注意です。

C言語側のプログラムは簡単ですな。 0xa0000 ~ 0xaffff 番地に 15 を書き込んでいます。

さてどうでしょう、みなさんは画面が真っ白になったでしょうか?

2 - しましま模様

AND演算が登場です。AND演算は2進数の世界でよく出てきます。こんな感じの演算です。

11111111 AND 10010001 = 10010001

筆算するとこんな感じです。

     11111111
AND) 10010001
-------------
     10010001

...法則性がわかったでしょうか?
AND演算は 同じ桁どうしを比べて、両方1だったら1、それ以外は0を出力する演算 です。コンピュータの世界ではよく出てくるので要チェックです。

本ではこれを使って縞模様を出力していますね。しましまになりましたか?

3 - ポインタに挑戦

さて幾多のプログラミング入門者を苦しませてきたポインタです。ポインタはアセンブリと密接な関係があるので、アセンブリと比較しながら見ていきましょう。

まずC言語にはメモリの番地に直接データを書き込むための表現があります。つまり以下のことをする表現です。

MOV    [0x7ffc],0x0f    ; メモリの0x7ffc番地にデータ0x0fを書き込む

これをC言語で表現しようとすると以下のようになります。

char *p;
p = (char *) 0x7ffc;
*p = 0x0f;

ゆっくり見ていきましょう。まず char *p; があります。どうやら変数宣言のようですが、変数 p の前に * がついています。この記号で 変数 p はメモリの番地 を表すことになります。つまり、この p をいじることによって、メモリのとある番地のデータを読み込んだり、これまたメモリのとある番地にデータを書き込んだりすることができるのです。
しかしなぜ宣言に char を用いているのでしょうか。というのもアセンブリC言語に対応があって、以下のようになっています。

char *p;  // BYTE用番地の場合
short *p;  // WORD用番地の場合
int *p;  // DWORD用番地の場合

今回は1バイト分のメモリ番地を指定したかったので char を用いています。

次に p = (char *) 0x7ffc; を見ましょう。これは p にメモリの番地を指定する作業 です。変数 p を宣言してメモリ内のデータを操りたいのですが、メモリのどの番地を操作するのかまだ指定してませんでした。ということでここで(例として) 0x7ffcp に指定しています。注意する点として、メモリの番地を指定するときは * はつけません。ここテストで出ます。 (char *) でキャストらしきことをしているのは、Cコンパイラではどうやら普通の数値とメモリの番地用の数値を区別するようで、警告が出るのでそれを回避するためです。

最後に *p = 0x0f; です。これは p に指定されたメモリ番地にデータを書き込むこと を表現しています。今回の場合は 0x7ffc 番地に 0x0f を書き込んでいますね。注意ですが、データを書きこむときは * をつけます。ここもテストで出ますので注意。

4 - ポインタの応用(1)

for (i = 0; i <= 0xffff; i++) {
    *(p + i) = i & 0x0f;
}

これは p + i 番地にデータ i & 0x0f を書き込んでいますね。for文で i が1つずつ増えていくので実はさっきとやっていることは変わりません。

5 - ポインタの応用(2)

for (i = 0; i <= 0xffff; i++) {
    p[i] = i & 0x0f;
}

p[i] という表現は *(p + i) と完全に同じです。なので p[i] == i[p] なのです…と言われても納得行かないかもしれません。でも自分の力ではこれ以上説明できそうにありません(泣) この本 とか読むといいんじゃないでしょうか。申し訳ない(汗)

6 - 色番号設定

色番号の章ですが、色番号の解説は割愛します。これはただの決まりですもんね。

さてこの章では、値をまとめて宣言する方法が紹介されます。 C言語の特性ですが、

char a[3];

と書くと、アセンブラでは

a:
    RESB  3

に相当します。でも微妙にアセンブラC言語では違いがあって、 RESB では0で埋めてくれましたが、C言語の書き方だと0で埋めてくれなくて、中にゴミデータが入っているかもしれませんので注意です。そのため、データの初期値を設定することもできます。

char a[3] = { 1, 2, 3 };

これは以下の表記とほぼ同等です。

char a[3];
a[0] = 1;
a[1] = 2;
a[2] = 3;

ここで注目してほしいのが、 a が最初からポインタとして認識されていることです。上のアセンブラを見てもらえるとわかるのですが、 a はラベルで、つまりメモリのどこかの番地を指しているわけです。

今回のプログラムだと、上の方法では48個の値を MOV しなくてはなりません。これは(機械語的に)大変なので DB 命令で一気に指定したいところです。C言語では DB 命令に相当する表記があって、それが static です。今回のプログラムでは static を宣言の前につけていますね。

次に bootpack.cset_palette() を解説します。

CPUにはメモリ以外にも沢山のデバイスが接続されていて、そこに命令を送るのが OUT 命令と IN 命令です。この命令は装置番号(portといいます)を指定してデータを送ったり受け取ったりします。この命令はC言語では書けないのでアセンブラで書く必要があります。

さてどんなことをすればいいのでしょうか。本によると、以下の操作をすれば良さそうです。

  1. 割り込みを禁止する
  2. 0x03c8 に設定したいパレット番号を書き込み、続いてR,G,Bの順に 0x03c9 に書き込む
  3. パレット状態の読み込みは、 0x03c7 にパレット番号を書き込み、続いて 0x03c9 を3回読み出す
  4. 割り込み状態をもとに戻す

割り込み禁止処理と割り込み許可処理ですが、 CLISTI が登場します。 CLI というのはclear interrupt flagの略で、割り込みフラグを0にして割り込みを禁止する処理です。 STI というのはset interrupt flagの略で、割り込みフラグを1にして割り込みを動作させる処理です。
割り込みが何かって?その説明はちょっと先になります。もう少し我慢。

次に EFLAGS という特別なレジスタの説明です。
簡単に言うといろんなフラグが詰まったレジスタで、キャリーフラグや割り込みフラグもここに入っています。キャリーフラグは JC とか JNC などで簡単に中身が調べられるのですが、割り込みフラグはそういう命令がないので、 EFLAGS を読み込んでから第9ビットが0か1かチェックするしかないのです。

f:id:hrstmyk811m:20190318092019p:plain
EFLAGSマップ

EFLAGSC言語からは操作できないのでアセンブラでがんばります。 naskfunc.nas へ移りましょう。

ここで私達がやりたいのはつまり MOV EAX,EFLAGS なわけですが、これはできません。そういう命令がないのでしょうがないです。なのでスタックを使う必要があります。スタックって何って?それはまた後日。
PUSHFDPOPFD 命令が初登場です。それぞれ、push flags double-wordとpop flags double-wordの略で、フラグをスタックに押し込む/取り出すという意味です。つまり、

_io_load_eflags:
        PUSHFD
        POP      EAX
        RET

というのは、スタックに PUSHFDELFAGS を押し込んで、 POP で飛び出してきたところを EAX に代入しているのです。

また値を返す関数ですが、C言語の規約では RET したときに EAX に入っていた値が戻り値としてみなされます。要チェックです。

7 - 四角形を描く

いままでのことを総動員して絵を描いていますね。特に難しそうなところはなさそうですね。

8 - 今日の仕上げ

根気よく絵を描いています。これ大変そうだなー

今日はここまで

お疲れ様でした〜それではまた明日〜
5日目は こちら