雑記

いち情報系大学生

OS学習メモ5(30日OS自作入門5日目)

張り切って5日目。
前回まででそれっぽい画面が出来たので、今回は文字を表示させてみたい。
f:id:unyamahiro:20180504140000p:plain
…まぁ本の通りやったら表示できますよそりゃぁね(笑)。でもそれだけじゃぁつまらないので、自分でも何かかいてみようと思った。考えた結果フレーミーを描くことにした。

それでっ \バン/(気力の音)
f:id:unyamahiro:20180504140131p:plain

とりあえずこんな感じになったのはいいが、本に書いてあるやり方では35x26マスのフォントなんて書けそうにないのでここからさらに…
f:id:unyamahiro:20180504140236p:plain
8x26に分割する。

OSASKのフォントファイルhankaku.txtを見たら、最後の方が使われていなかったので、ここに作ったやつを自分で手入力をして(8x26)、bootpack.cを(だいぶ無茶して)変えて…
f:id:unyamahiro:20180504140403p:plain
huremi関数を作って表示するところまで出来ました
(ここまで色々な格闘があった)

とまぁ3時間くらいかけてフォントを用意するところから自分でフレーミーが描けたわけです。こうして自分で作ってみて「英字のフォント、さらりと用意されているけどこれ用意するのとか大変だったんだろうなぁ」と思いました。思わされました(笑)。OSASKフォントを作った方に敬意を表して今後有り難〜く使わせてもらおうと思ったのでした(笑)。

さてさて。次はマウスを動かそうとのこと。
ここでGDTとIDTの初期化の話が出てくるが、その前にセグメンテーションの話。セグメンテーションは、ざっくりいうと「メモリを自由に切り分けて独立させること」で、これによって複数のプログラムをメモリ上でぶつからないように配置させたり、他のプログラムに不正にアクセスできないようにすることができる。こうして切り分けた一つひとつをセグメントと呼ぶが、このセグメントを表すには「セグメントの大きさ」「セグメントがどの番地から始まるか」「セグメントの管理用属性」の3つの情報が必要になり、CPUはこれらを64ビットのデータで表している。とは言っても、セグメントを指定するレジスタは16ビットしか無い。困った。

そこで別の方法を使う。
4日目でパレットを設定したときのように、セグメントセレクタと呼ばれる番号をそれぞれのセグメントとあらかじめ対応づけておいて、使う時は、このセグメントセレクタをセグメントレジスタで指定して使う。13ビット分(8192個)のセグメントを指定することができ、肝心のそれぞれのセグメント情報はセグメントディスクリプタテーブル(GDT)というやつに格納してメモリにおいておく。ごちゃごちゃしてきたので図で整理するとこんな感じ(セグメント番号って書いてあるのが上の説明でいうセグメントセレクタのこと)
f:id:unyamahiro:20180504163812p:plain
ちなみに、メモリの中にあるセグメントディスクリプタテーブルの先頭番地や有効設定個数などはGDTR(セグメントディスクリプタテーブルレジスタ)というレジスタに格納される。長くなったけど、ここまでがGDTの話。

IDTは割り込みディスクリプタテーブルのこと。(以前MINIX本で勉強して記事にもまとめた気がするけど)CPUでは、外部からの入力あるいはめったに起きない内部トラブルが起こると、その時点での処理を一旦中断して別の処理に切り替える機能があり、これを割り込みという。ここでの目的はマウスを動かすことで、マウスを使うには割り込みを使えるようにならなくてはならず、そのためにはIDTを設定する必要があるという流れ。IDTは、割り込み番号0-255に対して「割り込み番号123が発生したらOX関数を呼び出す」みたいな設定の表(GDTに似ている)。IDT設定のためにはセグメントの設定がきちんと完了していないといけないのでGDTを設定した。

コードの関係ありそうな所を見てみる(サンプルコピー)

struct SEGMENT_DESCRIPTOR {
	short limit_low, base_low;
	char base_mid, access_right;
	char limit_high, base_high;
};

struct GATE_DESCRIPTOR {
	short offset_low, selector;
	char dw_count, access_right;
	short offset_high;
};

void init_gdtidt(void);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
void load_gdtr(int limit, int addr);
void load_idtr(int limit, int addr);

void init_gdtidt(void)
{
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) 0x0026f800;
	int i;

	for (i = 0; i < 8192; i++) {
		set_segmdesc(gdt + i, 0, 0, 0);
	}
	set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
	set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
	load_gdtr(0xffff, 0x00270000);

	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	load_idtr(0x7ff, 0x0026f800);

	return;
}

void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
	if (limit > 0xfffff) {
		ar |= 0x8000;
		limit /= 0x1000;
	}

	sd->limit_low    = limit & 0xffff;
	sd->base_low     = base & 0xffff;
	sd->base_mid     = (base >> 16) & 0xff;
	sd->access_right = ar & 0xff;
	sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
	sd->base_high    = (base >> 24) & 0xff;
	return;
}

void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
	gd->offset_low   = offset & 0xffff;
	gd->selector     = selector;
	gd->dw_count     = (ar >> 8) & 0xff;
	gd->access_right = ar & 0xff;
	gd->offset_high  = (offset >> 16) & 0xffff;
	return;
}

まず気になるのが

for (i = 0; i < 8192; i++) {
	set_segmdesc(gdt + i, 0, 0, 0);
}

ここでの変数gdtにはセグメントディスクリプタの先頭アドレスが入っている。整数iを加えていくことで(ポインタなので番地は8ずつ増える)、全てのセグメントに対して「セグメントの大きさ0」と「番地0」と「アクセス権の属性0」を設定している。

次の行からは

set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
load_gdtr(0xffff, 0x00270000);

セグメント番号1番とセグメント番号2番に対して設定。CではGDTRに代入することが出来ないのでload_gdtr関数を呼んでアセンブラの力を借りて設定する。IDTも同じようなことをしている。

これにて5日目終了です。
さて、6日目に入ります。