メモリ領域@自作OS入門

〜30日間でできる自作OS〜

9日目

  1. メモリ容量チェック(2)
  2. メモリ管理

10日目

  1. (続)メモリ管理
  2. 重ね合わせ処理

メモリ容量チェック(2)

前日の続きです。
このメモリチェックは、実はうまくいきません。
実行してみると、メモリ・・・3072MB!?


なぜかというと、コンパイラの最適化が原因です。

比較してみます。
以下はコンパイル前のコードです。

unsigned int memtest_sub(unsigned int start, unsigned int end)
{
	unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
	for (i = start; i <= end; i += 0x1000) {
		p = (unsigned int *) (i + 0xffc);
		old = *p;			/* いじる前の値を覚えておく */
		*p = pat0;			/* ためしに書いてみる */
		*p ^= 0xffffffff;	/* そしてそれを反転してみる */
		if (*p != pat1) {	/* 反転結果になったか? */
not_memory:
			*p = old;
			break;
		}
		*p ^= 0xffffffff;	/* もう一度反転してみる */
		if (*p != pat0) {	/* 元に戻ったか? */
			goto not_memory;
		}
		*p = old;			/* いじった値を元に戻す */
	}
	return i;
}


このコードを、コンパイラは最適化は以下のように解釈するそうです。
(①などの番号は順番です)

unsigned int memtest_sub(unsigned int start, unsigned int end)
{
	unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
	for (i = start; i <= end; i += 0x1000) {
		p = (unsigned int *) (i + 0xffc);/*⑤一度も利用していないため、削除*/
		old = *p;			/*④値を取り出して格納しているだけなので、削除*/
		*p = pat0;			/*③直後に*p = oldが入る=無意味なため、削除*/
		*p ^= 0xffffffff;	/*②反転を2回繰り返しているため、削除*/
		if (*p != pat1) {	/*①ifが成立しないため、削除*/
not_memory:
		*p = old;
			break;
		}
		*p ^= 0xffffffff;	/*②反転を2回繰り返しているため、削除*/
		if (*p != pat0) {	/*①ifが成立しないため、削除*/
			goto not_memory;
		}
		*p = old;			/*④値を取り出して格納しているだけなので、削除*/
	}
	return i;
}

といった感じで進めていくと、結果こうなります。

unsigned int memtest_sub(unsigned int start, unsigned int end)
{
	unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
	for (i = start; i <= end; i += 0x1000) { }
	return i;
}


・・・なにもしないただのループ処理になってしまいました。
ここで、最適化をやめさせると他のコードに影響が出てしまうので、アセンブラで書くことに。

アセンブラで書くと、正常に動きました。


メモリ管理

メモリの管理は、構造体の配列を使って管理します。
アドレスとサイズを保持しています。
以下は構造体の宣言です

struct FREEINFO{//メモリ空き情報構造体
        unsigned int addr,size;
};

struct MEMMAN{//メモリ管理構造体
    int frees,maxfrees,lostsize,losts;
    struct FREEINFO free[MEMMAN_FREES];
};


まず、メモリテストをして、利用できるメモリサイズを取得します。
次に、メモリ管理構造体を初期化して、空きメモリ情報を既に格納されているメモリ空き情報構造体へ登録します。
実装部分はコード量が多いので、呼び出し部分だけ書き残します。

memtotal = memtest(0x00400000, 0xbfffffff);//メモリテスト+確保可能メモリサイズ
memman_init(memman);//メモリ管理構造体初期化
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
memman_free(memman, 0x00400000, memtotal - 0x00400000);


3行目のmemman_free(memman, 0x00001000, 0x0009e000)部分、なぜこのアドレスなのかはまだ不明です。


ここで、少し悩んだこと。ネーミングがmemman_free。
freeって解放の意味だと思うのですが、してることは管理リストに登録。
名前的にentry_memory_listとかなのでは。と思うのです。ううむ、認識が違うのかな。
一応、サンプルには、

int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
/* 解放 */

と書いてあります。

メモリ確保や解放と言えば、C言語malloc()やfree()ですが、この流れなのでしょうか。
ううむ、調べると時間かかりそうなので先に進みます。


(続)メモリ管理

まず、メモリ管理部分を別ファイルにしました。関数宣言をヘッダファイルに書き出して、メモリ管理用のCファイルを作成しました。

次にメモリ管理ですが、現在1バイト単位で管理しています。

memman_free(memman, 0x00001000, 0x00000012);//18バイトとかOK
memman_free(memman, 0x00001111, 0x00000001);//1バイトとかOK

できちゃいます。
これだと、連続しない小さな空きがたくさんできてしまうことがあると言われているそうです。
その結果、メモリ管理リストを使い切ってしまう(現状4090)可能性があるとのことなので、0x1000(4096≒4k)バイト単位で管理します。

unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size);
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size);

ここで、確保したいメモリに対して、確保するメモリの切り上げを行わなければなりません。
切り上げの仕組みとしては、“確保メモリ+(区切り単位-1) / 区切り単位”です。

size = (size + 0xfff) & 0xfffff000;

これで切り上げができました。
あとは、このように組み込んで・・・4kバイトごとに管理します。

unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
	unsigned int a;
	size = (size + 0xfff) & 0xfffff000;
	a = memman_alloc(man, size);
	return a;
}

重ね合わせ処理

話は変わって、画面の重ね合わせ処理です。
レイヤー的なイメージで、本書では透明な下敷きと例えています。
ん〜。レイヤーよりも下敷きのほうが親しみを感じるので、以降“下敷き”で統一します。
プログラム上では、この下敷きと、下敷きの管理する構造体を宣言しています。
今回は、下敷き用Cファイルを最初から分離しています。



以下は、下敷きと下敷き管理の構造体です

struct SHEET{//下敷き
    unsigned char *buf;
    int bxsize,bysize,vx0,vy0,col_inv,height,flags;
};

struct SHTCTL{//下敷き管理
    unsigned char *vram;
    int xsize,ysize,top;
    struct SHEET *sheets[MAX_SHEETS];
    struct SHEET sheets0[MAX_SHEETS];
};


SHTCTL構造体(下敷き管理)は、SHEET(下敷き)構造体を持っていて、参照をポインタ配列で管理しています。


下敷きの番号が大きいほど後に重ね合わせが発生します。(=画面の手前側というイメージ)
以下は重ね合わせのロジックです


下敷き情報を取得して透過色を抜いてVRAMへ配置しています。

void sheet_refresh(struct SHTCTL *ctl){
	int h, bx, by, vx, vy;
	unsigned char *buf, c, *vram = ctl->vram;
	struct SHEET *sht;
	for (h = 0; h <= ctl->top; h++) {
		sht = ctl->sheets[h];//下敷き取得
		buf = sht->buf;
		//下敷き情報を元にvramへ値を適用
		for (by = 0; by < sht->bysize; by++) {//height
			vy = sht->vy0 + by;
			for (bx = 0; bx < sht->bxsize; bx++) {//width
				vx = sht->vx0 + bx;
				c = buf[by * sht->bxsize + bx];
				if (c != sht->col_inv) {//透過色以外は反映
					vram[vy * ctl->xsize + vx] = c;
				}
			}
		}
	}
	return;
}


最後にレイヤーの入れ替えロジックがあります。
コード量が多いので全ては掲載できませんが、一部だけ。


下敷き管理構造体ポインタ+下敷きポインタ+配置レイヤー(0未満だと非表示という仕様)
を指定すると、配置レイヤーに格納してくれます

void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height);


配置レイヤー値が元々のレイヤー値より下の場合、それ以下の下敷きを押し上げて差し込みます

/* 既に配置済み=押し上げ */
//old=下敷きの元々のレイヤー値
//height=下敷きを差し込みたいのレイヤー値
for (h = old; h > height; h--) {
	ctl->sheets[h] = ctl->sheets[h - 1];
	ctl->sheets[h]->height = h;//押し上げた下敷きのレイヤー値更新
}
ctl->sheets[height] = sht;//差し込み


上のロジックと同じ要領で元々のレイヤー値より上の場合も実装。
ちなみに、非表示化はポインタを配列から消すだけです。

透過色処理のおかげでマウスポインタが今までは

だったのが、いい感じに表示されてます