「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
を宣言してメモリ内のデータを操りたいのですが、メモリのどの番地を操作するのかまだ指定してませんでした。ということでここで(例として) 0x7ffc
を p
に指定しています。注意する点として、メモリの番地を指定するときは *
はつけません。ここテストで出ます。 (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.c
の set_palette()
を解説します。
CPUにはメモリ以外にも沢山のデバイスが接続されていて、そこに命令を送るのが OUT
命令と IN
命令です。この命令は装置番号(portといいます)を指定してデータを送ったり受け取ったりします。この命令はC言語では書けないのでアセンブラで書く必要があります。
さてどんなことをすればいいのでしょうか。本によると、以下の操作をすれば良さそうです。
- 割り込みを禁止する
0x03c8
に設定したいパレット番号を書き込み、続いてR,G,Bの順に0x03c9
に書き込む- パレット状態の読み込みは、
0x03c7
にパレット番号を書き込み、続いて0x03c9
を3回読み出す - 割り込み状態をもとに戻す
割り込み禁止処理と割り込み許可処理ですが、 CLI
と STI
が登場します。 CLI
というのはclear interrupt flagの略で、割り込みフラグを0にして割り込みを禁止する処理です。 STI
というのはset interrupt flagの略で、割り込みフラグを1にして割り込みを動作させる処理です。
割り込みが何かって?その説明はちょっと先になります。もう少し我慢。
次に EFLAGS
という特別なレジスタの説明です。
簡単に言うといろんなフラグが詰まったレジスタで、キャリーフラグや割り込みフラグもここに入っています。キャリーフラグは JC
とか JNC
などで簡単に中身が調べられるのですが、割り込みフラグはそういう命令がないので、 EFLAGS
を読み込んでから第9ビットが0か1かチェックするしかないのです。
EFLAGS
もC言語からは操作できないのでアセンブラでがんばります。 naskfunc.nas
へ移りましょう。
ここで私達がやりたいのはつまり MOV EAX,EFLAGS
なわけですが、これはできません。そういう命令がないのでしょうがないです。なのでスタックを使う必要があります。スタックって何って?それはまた後日。
PUSHFD
と POPFD
命令が初登場です。それぞれ、push flags double-wordとpop flags double-wordの略で、フラグをスタックに押し込む/取り出すという意味です。つまり、
_io_load_eflags: PUSHFD POP EAX RET
というのは、スタックに PUSHFD
で ELFAGS
を押し込んで、 POP
で飛び出してきたところを EAX
に代入しているのです。
また値を返す関数ですが、C言語の規約では RET
したときに EAX
に入っていた値が戻り値としてみなされます。要チェックです。
7 - 四角形を描く
いままでのことを総動員して絵を描いていますね。特に難しそうなところはなさそうですね。
8 - 今日の仕上げ
根気よく絵を描いています。これ大変そうだなー
今日はここまで
お疲れ様でした〜それではまた明日〜
5日目は こちら