23日目 malloc
なんかすっかりフェードアウト気味…。
今回はmalloc。以下の方針で
- mallocの実装はここにある単純アロケータをまるっと使用。
- 上のmalloc実装に必要なsbrk(2)を実装。
- 実際のメモリ確保はページフォルトが起きた時点でおこなう。
- ページフォルトハンドラで確保されたメモリはアプリ終了時に解放。
まずはsbrk(2)から。
struct TASKにシステムブレークの下限、上限、現在値を持たせる。
struct TASK { int sel, flags; int level, priority; unsigned long start_brk, end_brk, brk; // ここ struct FIFO32 fifo; struct TSS32 tss; };
システムコールsys_brkを実装。名前も中身もほとんどLinuxそのまま。
ちなみにbrk(2)とは返り値が異る。
static unsigned long sys_brk(unsigned long brk) { struct TASK *task = task_now(); if (brk < task->start_brk || brk > task->end_brk) return task->brk; if (task->brk == brk) return task->brk; /* * Always allow shrinking brk */ if (brk <= task->brk) { task->brk = brk; return brk; } /* * Ok, looks good - let it rip. */ return task->brk = brk; } int hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { (略) if (edx == 1) { cons_putchar(cons, eax & 0xff, 1); } (略) } else if( edx == 8){ reg[7] = sys_brk((unsigned long)ebx); } return 0; }
brk(2), sbrk(2)を実装。これもほとんどLinuxそのまま。
extern int api_brk(void* addr); void* __curbrk = 0; int brk(void *addr) { void *newbrk; newbrk = api_brk(addr); __curbrk = newbrk; if (newbrk < addr) { return -1; } return 0; } void *sbrk(int increment) { void *oldbrk; if (__curbrk == 0){ if (brk(0) < 0){ /* Initialize the break. */ return (void *) -1; } } if (increment == 0) return __curbrk; oldbrk = __curbrk; if (brk(oldbrk + increment) < 0) return (void *) -1; return oldbrk; }
これでsbrk(2)ができたので、あとは実際にアロケート処理をおこなうページフォルトの部分を作る。
ページフォルト例外は0x0Eで例外ハンドラはこんなかんじ。
.globl asm_inthandler0e asm_inthandler0e: pushw %es pushw %ds pusha movl %cr3, %eax pushl %eax movl %cr2, %eax pushl %eax movl %ds, %eax pushl %eax movl %esp, %eax pushl %eax movw %ss, %ax movw %ax, %ds movw %ax, %es call inthandler0e cmp $0, %eax jne end_app0e addl $16, %esp popa popw %ds popw %es addl $4, %esp iret end_app0e: // %eaxはtss.esp0の番地 movl (%eax), %esp popa ret // cmd_appへ帰る
CR3はページディレクトリテーブルの実アドレス、CR2はページフォルトを起こしたアドレス。
int *inthandler0e(int *esp, unsigned long ds, unsigned long cr2, unsigned long* pageDir) { char s[30]; struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); struct TASK *task = task_now(); struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; unsigned long accs_addr; unsigned long* dir_ent; unsigned char* addr_ent; /* カーネルデータセグメントなら異常停止 */ if(8 == ds) goto oops_proc; if(cr2 < task->start_brk || cr2 >= task->brk) goto err_proc; accs_addr = cr2 & 0xFFFFF000; /* ページディレクトリのエントリを取得 */ if(!pageDir[accs_addr >> 22]){ /* エントリがないので作成 */ dir_ent = (unsigned long*)memman_alloc_4k_boundary(memman, 0x1000); memset(dir_ent, 0x00, 0x1000); pageDir[accs_addr >> 22] = MakePTE((unsigned long)dir_ent); } else { dir_ent = pageDir[accs_addr >> 22] & 0xFFFFF000; } /* ページテーブルのエントリを作成 */ addr_ent = (unsigned char*)memman_alloc_4k_boundary(memman, 0x1000); dir_ent[((accs_addr) >> 12) & 0x3FF] = MakePTE(addr_ent); load_cr3(pageDir); return 0; err_proc: cons_putstr0(cons, "\nINT 0E :\n Page Fault.\n"); sprintf(s, "CR2 = %08X\n", cr2); cons_putstr0(cons, s); return &(task->tss.esp0); /* 異常終了させる */ oops_proc: cons_putstr0(cons, "Kernel Oops.\n"); cons_putstr0(cons, "\nINT 0E :\n Page Fault.\n"); sprintf(s, "CR2 = %08X\n", cr2); cons_putstr0(cons, s); hlt(); /* 異常停止 */ return 0; }
あとはstart_appの前にヒープ領域を設定してやればいい。
例えば0xC0000000-0xFFFF0000をヒープ領域にしてみる。
task->brk = task->start_brk = 0xC0000000; task->end_brk = 0xFFFF0000;
これでアプリ内でmallocが使えるようになった。
最後にアプリ終了時に確保したメモリを解放。
void clear_heap(struct TASK* task) { unsigned long* pageDir; unsigned long* ent; unsigned long vir_addr, phy_addr; unsigned long i; struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; if(!(pageDir = (unsigned long*)(task->tss.cr3 & 0xFFFFF000))) return; for(vir_addr = task->start_brk & 0xFFFFF000 ; vir_addr < task->end_brk; vir_addr += 0x1000){ if(!(ent = pageDir[vir_addr >> 22])) continue; for(i = 0; i < 1024; i++){ if(!(phy_addr = ent[i] & 0xFFFFF000)) continue; memman_free_4k(memman, phy_addr, 0x1000); } } return; }