番外編 その2 アプリのページング

前回に続きページング。今度はコンソールからのアプリ実行時にページング設定を切り替えてみる。
今回の方針は以下のとおり。

  1. 0x00000000から4MB分をリニアアドレス==物理アドレスとする。
  2. アプリのエントリポイントはLinuxにならって0x08048000とする。
  3. アプリのスタックは0xFFFFFFFFから16KBとする。
  4. まだページフォルトハンドラは書かない。


コードの読み込みから実行、後始末までのコードはこんな感じに書いてみた。

	// 実行コードを読み込む.
	ext2_loadfile(ptr_inode, ptr_inode->i_size, p);

	// cs_baseは不要なので0にしておく.
	**1;

	// CR3を元にもどす.
	task->tss.cr3 = last_cr3;
	load_cr3(last_cr3);

	// ページテーブルを解放
	ClearPTE(cr3);

ext2_loadfileはメモリ上に展開されたext2fsのイメージからinodeを指定してファイルを読み込む関数。(ここで説明)

SetUpPTEでは4MBフレームをつかって物理アドレスの前半4MBをリニアアドレスの前半4MBに割り当て。コード、スタックには4KBフレームをつかってそれぞれ0x08048000からコードサイズ分、0xFFFFC000から16KB分(0xFFFFFFFFまで)のリニアアドレスを割り当て。(コードは後に記載)

コードセグメント、データセグメントは0x00000000から0xFFFFFFFFまでの4GBをまるっと指定。
TSSのCR3とCR3レジスタにページディレクトリテーブルの先頭アドレスを格納したら後はエントリポイントとスタックアドレスを指定してStart_appでアププリスタート。

帰ってきたらTSSのCR3とCR3レジスタを元にもどして、ページテーブルのために取得したメモリをClearPTE(コードは後に記載)で解放しておしまい。


エントリポイントが変更になるのでアプリ用のリンカスクリプトも変更が必要。具体的にはロケーションカウンタを使って.textセクション以降がエントリポイントから始まるようにリンカに指示する。

SECTIONS {
  . = 0x08048000;	←ここ
  .text   : { *(.text) }   /* Executable codes */
  .data   : { *(.data) }   /* Initialized data */
  .bss    : { *(.bss) }    /* Uninitialized data */
  .rodata : { *(.rodata*) } /* Constant data (R/O) */
}


以上の変更でアプリ用ページングが動作する。


SetUpPTE

#define PAGESIZE 4096

unsigned long SetUpPTE(unsigned char* appaddr, int appsize)
{
	int i;
	unsigned long* PageDir;
	unsigned long* addr;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	unsigned long viraddr;
	unsigned long staaddr, stasize;

	PageDir = (unsigned long*)memman_alloc_4k_boundary(memman, 0x1000);
	memset(PageDir, 0x00, 0x1000);

	// 前半4MBはリニア==物理とする.
	PageDir[0] = MakePTE_PSE(0x00);

	viraddr = APP_START_ADDR;
	i = (viraddr >> 12) & 0x3FF;
	while(appsize > 0){
		addr = (unsigned long*)memman_alloc_4k_boundary(memman, 0x1000);
		memset(addr, 0x00, 0x1000);
		PageDir[viraddr >> 22] = MakePTE((unsigned long)addr);
		for(;i < 1024 && appsize > 0; i++, appsize -= PAGESIZE, appaddr += PAGESIZE){
			addr[i] = MakePTE(appaddr);
		}
		if(appsize <= 0) break;
		viraddr += PAGESIZE * 1024;
		i = 0;
	}

	// スタックの設定.
	viraddr = 0xFFFFC000;	// 16KBのスタックサイズ.
	stasize = 16 * 1024;
	
	staaddr = memman_alloc_4k_boundary(memman, 16 * 1024);

	// テーブルエントリインデックス
	i = (viraddr >> 12) & 0x3FF;
	while(stasize > 0){
		addr = memman_alloc_4k_boundary(memman, 0x1000);
		memset(addr, 0x00, 0x1000);
		PageDir[viraddr >> 22] = MakePTE(addr);
		for(;i < 1024 && stasize > 0; i++, stasize -= PAGESIZE, staaddr += PAGESIZE){
			addr[i] = MakePTE(staaddr);
		}
		viraddr += PAGESIZE * 1024;
		i = 0;
	}
	return (unsigned long)PageDir;
}


ClearPTE

void ClearPTE(unsigned long* pageDir)
{
	int i;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;

	for(i = 0; i < 1024; i++){
		// エントリがなかったり、4MBページならなにもしない
		if(pageDir[i] == 0 || pageDir[i] & PTE_PSE)
			continue;
		
		memman_free_4k(memman, pageDir[i] & 0xfffff000, 0x1000);
	}

	memman_free_4k(memman, pageDir, 0x1000);
	return;
}

いまのところ問題なさげ。でも当初先頭の4MBにあるコードが間違って実行されてるのに気づかず、うまくいったと思いこんでた経緯があるので油断は禁物。

*1:int *) 0xfe8) = (int)0; // ページテーブルの設定 cr3 = SetUpPTE(p, ptr_inode->i_size); // アプリ用コードセグメント設定.(4GB) set_segmdesc(gdt + 1003, 0xFFFFFFFF, (int)0x0, AR_CODE32_ER + 0x60); // アプリ用データセグメント設定.(4GB) set_segmdesc(gdt + 1004, 0xFFFFFFFF, (int)0x0, AR_DATA32_RW + 0x60); // 現在のCR3を記憶しておく. last_cr3 = task->tss.cr3; // CR3を設定. task->tss.cr3 = cr3; load_cr3(cr3); // エントリポイントをAPP_START_ADDR(=0x08048000) // スタックを0xFFFFFFFFにしてアプリスタート start_app(APP_START_ADDR, 1003 * 8, 0xFFFFFFFF, 1004 * 8, &(task->tss.esp0