# # Threads for MIPS. Sample solution. bsy, created. # # void thr_init(thread_state, thr_func, arg, stack, stack_size) # # void thr_yield(void) # # void thr_go(void) # # user's main code may call thr_init several times to initialize several # threads, then call thr_go to go multithreaded. Threads run, and yields # occur in a round-robin fashion, until all the threads complete. # # # Thread state: # saved registers offset # s0 0 # s1 4 # s2 8 # s3 12 # s4 16 # s5 20 # s6 24 # s7 28 # gp 32 # ra 36 # fp 40 # sp 44 # runnable-flag 48 # # see header.mips # .data num_threads: .word 0 max_threads: .word 0 thread_tbl: .space 1024 # 256 threads possible cur_thread: .word 0 .text # # thr_init(thread_state, thr_func, arg, stack, stack_size) LEAF # .globl thr_init thr_init: lw $t0, 4($sp) # stack_size on stack addu $t0,$a3,$t0 # get to top of stack subu $t0,$t0,4 # addr first avail location sw $t0, SPOFF($a0) sw $a1, INIT_PC($a0) sw $a2, ARG($a0) sw $zero, STATE($a0) # INITED lw $t0, num_threads sll $t1, $t0, 2 la $t1, thread_tbl($t1) sw $a0, 0($t1) addu $t0, $t0, 1 sw $t0, num_threads jr $ra # # void thr_go(void) -- the main thread is a thread so that thr_yield # will always have some thread to run. This thread simply checks # the number of live threads and yields if there are others. # # frame pointer not saved, since no local variables # .data main_thread_data: .space THR_STATE_SIZE .text .globl thr_go thr_go: subu $sp, $sp, 4 sw $ra, 4($sp) lw $t0, num_threads sw $t0, cur_thread addu $t1, $t0, 1 sw $t1, num_threads # number left alive sw $t1, max_threads # wrap-around counting sll $t1, $t0, 2 la $t1, thread_tbl($t1) la $t2, main_thread_data sw $t2, 0($t1) li $t3, RUNNABLE sw $t3, STATE($t2) thr_go_loop: jal thr_yield lw $t0, num_threads bgt $t0, 1, thr_go_loop sw $zero, num_threads lw $ra, 4($sp) # we are alone! addu $sp, $sp, 4 jr $ra # # void thr_yield(void) # # First, we save the register set of the current thread, then we # find next initialized or runnable thread, and context switch to it # (i.e., load its register set). This means is that if thread is in # the INIT state, we must first initialize the thread's registers so # its $RA points to our code to handle thread completion, load the # basic thread registers (so it looks like a regular jal call), mark # the thread as RUNNABLE, and jump to the thread function; if the thread # is already running (i.e., marked as RUNNABLE), we just do a simple # context switch. # .text .globl thr_yield thr_yield: lw $t0, cur_thread # save current context sll $t1, $t0, 2 lw $t1, thread_tbl($t1) sw $s0, S0OFF($t1) sw $s1, S1OFF($t1) sw $s2, S2OFF($t1) sw $s3, S3OFF($t1) sw $s4, S4OFF($t1) sw $s5, S5OFF($t1) sw $s6, S6OFF($t1) sw $s7, S7OFF($t1) sw $gp, GPOFF($t1) sw $ra, RAOFF($t1) sw $fp, FPOFF($t1) sw $sp, SPOFF($t1) lw $t0, cur_thread find_next: # $t0 must contain cur_thread addiu $t0, $t0, 1 lw $t1, max_threads blt $t0, $t1, no_wrap move $t0, $zero no_wrap: sw $t0, cur_thread sll $t1, $t0, 2 lw $t1, thread_tbl($t1) lw $t2, STATE($t1) bgtu $t2, RUNNABLE, find_next # invar check bnez $t2, simple_context_switch # # initialize the thread # li $t2, RUNNABLE sw $t2, STATE($t1) lw $sp, SPOFF($t1) lw $a0, ARG($t1) lw $t2, INIT_PC($t1) jalr $t2 # current runnable thread now finished # mark as exited, then find next runnable # and context switch to it lw $t0, cur_thread sll $t1, $t0, 2 lw $t1, thread_tbl($t1) li $t2, EXITED sw $t2, STATE($t1) lw $t3, num_threads subu $t3, $t3, 1 sw $t3, num_threads b find_next # invar check simple_context_switch: lw $s0, S0OFF($t1) # load new reg set lw $s1, S1OFF($t1) lw $s2, S2OFF($t1) lw $s3, S3OFF($t1) lw $s4, S4OFF($t1) lw $s5, S5OFF($t1) lw $s6, S6OFF($t1) lw $s7, S7OFF($t1) lw $gp, GPOFF($t1) lw $ra, RAOFF($t1) lw $fp, FPOFF($t1) lw $sp, SPOFF($t1) jr $ra