番外編 簡単なページング

なんとなくページングを試しみたくなったので、おためしとして以下の方針で実装してみることにする。

  1. 物理メモリ全てをリニアアドレス == 物理アドレスとなるように登録。
  2. ページフレームサイズは4MB。
  3. ページフォルトハンドラは実装しない


なおOS自作本にはページングの解説はないので以下の書籍を参考にした。
はじめて読む486―32ビットコンピュータをやさしく語る
Linuxのブートプロセスをみる (UNIXMAGAZINE COLLECTION)


bootpack.c内でメモリの初期化をした後、ページングに関する処理をおこなっていくことにする。

/* bootpack.c */
dirEntNum = (int)(memtotal / PAGESIZE_4MB) + 1;
pageDir = (unsigned long*)memman_alloc_4k_boundary(memman, dirEntNum);
for(i = 0; i < dirEntNum ; i++, phaddr += PAGESIZE_4MB){
	pageDir[i] = MakePTE_PSE((unsigned long)phaddr);
}

まずpageDirにメモリを割り当てて物理メモリ分MakePTE_PSEでPTEを作成して格納。
(memman_alloc_4k_boundaryはメモリが4K境界に配置されるように作成した関数)

以下MakePTE_PSEの定義

/* page.h */
#define PTE_PRESENT		0x01	// Pビット
#define PTE_RW			0x02	// R/Wビット
#define PTE_USER		0x04	// U/Sビット
#define PTE_PSE			0x80	// PSEビット

/* page.c */
unsigned long MakePTE_PSE(unsigned long physaddr)
{
	return (physaddr & 0xffc00000)
		| PTE_RW | PTE_USER | PTE_PRESENT | PTE_PSE;
}

今回は特権レベル3でアクセスできないとまずいのでU/S、R/Wビットを1にする。
あと4MBフレームなのでPSEビットも1にしておく。


これでページディレクトリテーブルの設定ができたので、あとはレジスタを適当に設定してページングをONにするだけ。

set_pse_cr4();
load_cr3((unsigned long)pageDir);
paging_start();

それぞれ

.globl load_cr3	// void load_cr3(unsigned long PageDirAddr)
load_cr3:		// レジスタCR3にアドレス(物理アドレス)を設定する関数
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	movl	%eax, %cr3
	popl	%ebp
	ret

.globl set_pse_cr4 // void set_pse_cr4(void)
set_pse_cr4:
	movl	%cr4, %eax
	orl	$0x0010, %eax
	movl	%eax, %cr4
	ret

.globl paging_start	// void paging_start(void) ページングの開始
paging_start:
	movl	%cr0, %eax
	orl	$0x80000000, %eax
	movl	%eax, %cr0
	ret

set_pse_cr4で4MBフレームを有効にするためcr4レジスタを設定。
load_cr3でcr3レジスタにページディレクトリテーブルを設定。
最後にpaging_startでcr0レジスタのPGビットを1にすれば次の命令よりページングが有効になる。


あとは各タスクのtss.cr3にpageDirの値が設定されるようにしてやればOK。



以下memman_alloc_4k_boundaryの定義

unsigned int memman_alloc_4k_boundary(struct MEMMAN *man, unsigned int size)
{
	unsigned int addr, mem_head_4k, offset;
	if( size < 0x1000)
		size = 0x1000;
	size = (size + 0xfff) & 0xfffff000; // 4KB単位にする.
	size += 0x1000;	// 4KB足す.
	addr = memman_alloc(man, size);

	// addrがNULLなら即座にかえる.

	if(addr == 0) return 0;

    // 先頭を4KB境界にする.
    // mem_head_4k: 4KBアライメントされた先頭.
	mem_head_4k = (addr + 0xfff) & 0xfffff000;
	offset = mem_head_4k - addr;
	if (offset > 0) {
        // アライメントがずれている.
		memman_free( man, addr, offset );
	}

    // いらない後ろを返す.
	memman_free( man, mem_head_4k + size - 0x1000, 0x1000 - offset );

	return mem_head_4k;

}

いかにもバグありそげな雰囲気…まぁとりあえず動いてるからいっか
案の定バグってたので修正(10/25)
id:sumomonekoさんにコメントもらってダメコード修正。(11/14)