システムコールの実装

ローダはとりあえずこの間簡易的に実装したものを使うことにして、とっととシステムコールを実装してみた。

システムコールのテーブルは、こんな感じに

void *syscall_functions[SYSCALL_MAX] = {
	(void *)putchar,
	(void *)getchar,
	(void *)getattr,
	(void *)opendir,
	(void *)readdir,
	(void *)seekdir,
	(void *)telldir,
	(void *)closedir,
	(void *)open,
	(void *)read,
	(void *)lseek,
	(void *)close,
};

単なる配列に。

ユーザランド側では、$v0をシステムコールナンバにして、引数・返り値のコンベンションは一般の関数呼び出しと同じに。

#define SYSCALL_ENTER(no)			\
	asm volatile("li $v0, %0\n"		\
		     "syscall" :: "i"(no))

void putchar(int c)
{
	SYSCALL_ENTER(SYSCALL_PUTCHAR);
}

カーネル側ではSystem Callを一般例外ベクタで受け取り、スタックにレジスタを保存してtrap()を呼び出し。

	subu	$sp, $sp, THREAD_MD_SIZE
	mfc0	$k1,COP0_STATUS
	sw	$k1,THREAD_MD_SR($sp)
	mtc0	$zero,COP0_STATUS
	mfc0	$k0,COP0_TLB_LO
	sw	$k0,THREAD_MD_TLB_LO($sp)
	mfc0	$k0,COP0_TLB_HI
	sw	$k0,THREAD_MD_TLB_HI($sp)
	mfc0	$k0,COP0_TLB_INX
	sw	$k0,THREAD_MD_TLB_INX($sp)
	mfc0	$k0,COP0_TLB_CTX
	sw	$k0,THREAD_MD_TLB_CTX($sp)
	sw	$at,THREAD_MD_AT($sp)
	sw	$v0,THREAD_MD_V0($sp)
	sw	$v1,THREAD_MD_V1($sp)
	sw	$a0,THREAD_MD_A0($sp)
	sw	$a1,THREAD_MD_A1($sp)
	sw	$a2,THREAD_MD_A2($sp)
	sw	$a3,THREAD_MD_A3($sp)
	sw	$t0,THREAD_MD_T0($sp)
	sw	$t1,THREAD_MD_T1($sp)
	sw	$t2,THREAD_MD_T2($sp)
	sw	$t3,THREAD_MD_T3($sp)
	sw	$t4,THREAD_MD_T4($sp)
	sw	$t5,THREAD_MD_T5($sp)
	sw	$t6,THREAD_MD_T6($sp)
	sw	$t7,THREAD_MD_T7($sp)
	sw	$s0,THREAD_MD_S0($sp)
	sw	$s1,THREAD_MD_S1($sp)
	sw	$s2,THREAD_MD_S2($sp)
	sw	$s3,THREAD_MD_S3($sp)
	sw	$s4,THREAD_MD_S4($sp)
	sw	$s5,THREAD_MD_S5($sp)
	sw	$s6,THREAD_MD_S6($sp)
	sw	$s7,THREAD_MD_S7($sp)
	sw	$t8,THREAD_MD_T8($sp)
	sw	$t9,THREAD_MD_T9($sp)
	sw	$gp,THREAD_MD_GP($sp)
	sw	$fp,THREAD_MD_FP($sp)
	sw	$ra,THREAD_MD_RA($sp)
	mfhi	$k0
	sw	$k0,THREAD_MD_HI($sp)
	mflo	$k0
	sw	$k0,THREAD_MD_LO($sp)
	mfc0	$k0,COP0_BAD
	sw	$k0,THREAD_MD_BAD($sp)
	mfc0	$k0,COP0_EPC
	sw	$k0,THREAD_MD_PC($sp)
	mfc0	$a1,COP0_CAUSE
	sw	$a1,THREAD_MD_CAUSE($sp)
	la	$gp,_gp
	move	$a0,$k1
	move	$a2,$k0
	move	$a3,$sp
	jal	trap
	nop

trap()では割り込み要因を特定してsyscall_handle()を呼び出し。

void 
trap(unsigned status, unsigned cause, unsigned epc, 
     md_thread_t *frame)
{
	const int exc = cause & CAUSE_EXC_MASK;
	DPRINTF("status:%x cause:%x epc:%x exc:%x\n",
		status, cause, epc, exc);
	switch(exc) {
	case EXC_INT:
		interrupt_handle(cause & CAUSE_IP_MASK);
		break;
	case EXC_SYS:
		syscall_handle(status, cause, epc, frame);
		break;
	default:
		printf("[general exception]\n");
		dump_frame(frame);
		while(1)
			;
	}
}

syscall_handle()ではframeからレジスタを取り出してシステムコール関数を呼び出し、帰り値をframeに戻す。
また、返り先のPCを調整(ディレイスロットの場合など複雑な条件は考慮出来ておらず、単に次の命令へ戻るようにしている)。

typedef unsigned (*syscall_func_t)(unsigned, unsigned, unsigned, unsigned);

void syscall_handle(unsigned status, unsigned cause, unsigned epc, 
		    md_thread_t *frame)
{
	DPRINTF("syscall code:%x arg0:%x arg1:%x arg2:%x arg3:%x\n",
		frame->v0, frame->a0, frame->a1, frame->a2, frame->a3);

	if(cause & CAUSE_BD) {
		panic("delay slot is not supported in syscall\n");
	}else
		frame->pc = epc + sizeof(int);
	syscall_func_t syscall_function =
		(syscall_func_t)syscall_functions[frame->v0];
	frame->v0 = 
		syscall_function(frame->a0, frame->a1, frame->a2, frame->a3);
}

frameからレジスタを復帰して、epcへ戻る。

	lw	$k0,THREAD_MD_TLB_LO($sp)
	mtc0	$k0,COP0_TLB_LO
	lw	$k0,THREAD_MD_TLB_HI($sp)
	mtc0	$k0,COP0_TLB_HI
	lw	$k0,THREAD_MD_TLB_INX($sp)
	mtc0	$k0,COP0_TLB_INX
	lw	$k0,THREAD_MD_TLB_CTX($sp)
	mtc0	$k0,COP0_TLB_CTX
	lw	$at,THREAD_MD_AT($sp)
	lw	$v0,THREAD_MD_V0($sp)
	lw	$v1,THREAD_MD_V1($sp)
	lw	$a0,THREAD_MD_A0($sp)
	lw	$a1,THREAD_MD_A1($sp)
	lw	$a2,THREAD_MD_A2($sp)
	lw	$a3,THREAD_MD_A3($sp)
	lw	$t0,THREAD_MD_T0($sp)
	lw	$t1,THREAD_MD_T1($sp)
	lw	$t2,THREAD_MD_T2($sp)
	lw	$t3,THREAD_MD_T3($sp)
	lw	$t4,THREAD_MD_T4($sp)
	lw	$t5,THREAD_MD_T5($sp)
	lw	$t6,THREAD_MD_T6($sp)
	lw	$t7,THREAD_MD_T7($sp)
	lw	$s0,THREAD_MD_S0($sp)
	lw	$s1,THREAD_MD_S1($sp)
	lw	$s2,THREAD_MD_S2($sp)
	lw	$s3,THREAD_MD_S3($sp)
	lw	$s4,THREAD_MD_S4($sp)
	lw	$s5,THREAD_MD_S5($sp)
	lw	$s6,THREAD_MD_S6($sp)
	lw	$s7,THREAD_MD_S7($sp)
	lw	$t8,THREAD_MD_T8($sp)
	lw	$t9,THREAD_MD_T9($sp)
	lw	$gp,THREAD_MD_GP($sp)
	lw	$fp,THREAD_MD_FP($sp)
	lw	$ra,THREAD_MD_RA($sp)
	lw	$k0,THREAD_MD_HI($sp)
	mthi	$k0
	lw	$k0,THREAD_MD_LO($sp)
	mtlo	$k0
	lw	$k0,THREAD_MD_BAD($sp)
	mtc0	$k0,COP0_BAD
	lw	$k1,THREAD_MD_PC($sp)
	mtc0	$k1,COP0_EPC
	lw	$a0,THREAD_MD_CAUSE($sp)
	mtc0	$a0,COP0_CAUSE
	lw	$k0,THREAD_MD_SR($sp)
	mtc0	$k0,COP0_STATUS
	addu	$sp, $sp, THREAD_MD_SIZE
	j	$k1
	rfe