システムコールの実装
ローダはとりあえずこの間簡易的に実装したものを使うことにして、とっととシステムコールを実装してみた。
システムコールのテーブルは、こんな感じに
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