23日目 malloc

なんかすっかりフェードアウト気味…。


今回はmalloc。以下の方針で

  1. mallocの実装はここにある単純アロケータをまるっと使用。
  2. 上のmalloc実装に必要なsbrk(2)を実装。
  3. 実際のメモリ確保はページフォルトが起きた時点でおこなう。
  4. ページフォルトハンドラで確保されたメモリはアプリ終了時に解放。

まずは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;
}