番外編 簡単なページング
なんとなくページングを試しみたくなったので、おためしとして以下の方針で実装してみることにする。
なお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)