# # Threads for MIPS. Sample solution 2. bsy, created. # # This one schedules newly created threads in the pause-run-pause-run # pattern. # # void thr_init(thread_state, thr_func, arg, stack, stack_size) # # void thr_yield(void) # # void thr_go(void) # # void thr_set_pri(thread_state, pri) # # void lock_init(lockptr) # # void lock_acquire(lockptr) # # void lock_release(lockptr) # # 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, with the thread priority specifying how # often a thread is run when encountered, until all the threads complete. # A thread running (after thr_go) may also called thr_init to create and # start new threads. # # # Thread state -- 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 sw $zero, SKIP_COUNT($a0) sw $zero, PRIORITY($a0) # 0 same as 1 # # insert $a0 into thread table. first, find a slot. # lw $t0, max_threads move $t2, $zero b thr_init_test thr_init_scan: sll $t3, $t2, 2 lw $t3, thread_tbl($t3) lw $t5, STATE($t3) beq $t5, EXITED, found_slot addu $t2, $t2, 1 thr_init_test: blt $t2, $t0, thr_init_scan addu $t2, $t0, 1 sw $t2, max_threads b thr_init_slot # t0 is still max_threads found_slot: move $t0, $t2 thr_init_slot: # # insert into slot at $t0 # sll $t1, $t0, 2 sw $a0, thread_tbl($t1) lw $t1, num_threads addu $t1, $t1, 1 sw $t1, 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) sw $zero, PRIORITY($t2) thr_go_loop: jal thr_yield lw $t0, num_threads bgt $t0, 1, thr_go_loop sw $zero, num_threads sw $zero, max_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) # # incr and check skip count to implement priority # lw $t3, SKIP_COUNT($t1) lw $t4, PRIORITY($t1) addi $t3, $t3, 1 bge $t3, $t4, okay_to_run sw $t3, SKIP_COUNT($t1) b find_next okay_to_run: sw $zero, SKIP_COUNT($t1) lw $t2, STATE($t1) bgtu $t2, RUNNABLE, find_next # invar check bne $t2, $zero, 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 # # void thr_set_pri(thread_state, priority) # .text .globl thr_set_pri thr_set_pri: sw $a1, PRIORITY($a0) jr $ra # # void lock_init(lockptr) LEAF # .text .globl lock_init lock_init: sw $zero, 0($a0) jr $ra # # void lock_acquire(lockptr) # .text .globl lock_acquire lock_acquire: subu $sp, $sp, 8 sw $a0, 8($sp) sw $ra, 4($sp) b lock_acquire_test lock_acquire_bod: jal thr_yield lock_acquire_test: lw $a0, 8($sp) lw $t0, 0($a0) bne $t0, $zero, lock_acquire_bod li $t0, 1 sw $t0, 0($a0) lw $ra, 4($sp) addu $sp, $sp, 8 jr $ra # # void lock_release(lockptr) LEAF # .text .globl lock_release lock_release: sw $zero, 0($a0) jr $ra