~/classes/6828/xv6$ **make qemu**
…
$ **uthread**
thread_a started
thread_b started
thread_c started
thread_c 0
thread_a 0
thread_b 0
thread_c 1
thread_a 1
thread_b 1
…
thread_c 99
thread_a 99
thread_b 99
thread_c: exit after 100
thread_a: exit after 100
thread_b: exit after 100
thread_schedule: no runnable threads
Hints
complete thread_create to create a properly initialized thread so that when the scheduler switches to that thread for the first time decide where to save/restore registers. You can add fields to struct thread into which to save registers.
Set up ra return address register to point to the thread function, so switch thread will jump to the function.
In the standard RISC-V calling convention, the stack grows downward and the stack pointer is always kept 16-byte aligned.
Set stack pointer to stack top.
Implement context switch
.text/* Switch from current_thread to next thread_thread, and make * next_thread the current_thread. Use t0 as a temporary register, * which should be caller saved. *//*Caller-saved registers (AKA volatile registers, or call-clobbered) are used to hold temporary quantities that need not be preserved across calls.Callee-saved registers (AKA non-volatile registers, or call-preserved) are used to hold long-lived values that should be preserved across calls.*/ .globl thread_switchthread_switch: /* YOUR CODE HERE */ sd ra,0(a0) sd sp,8(a0) sd s0,16(a0) sd s1,24(a0) sd s2,32(a0) sd s3,40(a0) sd s4,48(a0) sd s5,56(a0) sd s6,64(a0) sd s7,72(a0) sd s8,80(a0) sd s9,88(a0) sd s10,96(a0) sd s11,104(a0) ld ra,0(a1) ld sp,8(a1) ld s0,16(a1) ld s1,24(a1) ld s2,32(a1) ld s3,40(a1) ld s4,48(a1) ld s5,56(a1) ld s6,64(a1) ld s7,72(a1) ld s8,80(a1) ld s9,88(a1) ld s10,96(a1) ld s11,104(a1) ret /* return to ra */
We save ra, sp, and all callee saved registers. This assembly function is called be thread scheduler, to switch from current thread to next runnable one. This function saves registers info in thread->context, and load registers from new thread’s context, then jump executing the new thread.
Complete thread scheduler
Define struct and vars
struct thread {char stack[STACK_SIZE]; /* the thread's stack */int state; /* FREE, RUNNING, RUNNABLE */struct context context; // swtch() here to run process};struct thread all_thread[MAX_THREAD];struct thread *current_thread;externvoidthread_switch(struct context*,struct context*);
Find next runnable thread
struct thread *t,*next_thread; next_thread =0; t = current_thread +1;for(int i =0; i < MAX_THREAD; i++){if(t >= all_thread + MAX_THREAD) t = all_thread;if(t->state == RUNNABLE) { next_thread = t;break; } t = t +1; }
Try to find next one, if reaches the end then start from beginning. If none of them is available, current_thread is unchanged and resume.
Switch thread
if (current_thread != next_thread) {next_thread->state = RUNNING; t = current_thread; current_thread = next_thread;// Invoke thread_switch to switch from t to next_thread:thread_switch(&t->context,¤t_thread->context); }
Ref: Full Uthread Package
#include"kernel/types.h"#include"kernel/stat.h"#include"user/user.h"*/* Possible states of a thread: */*#defineFREE0x0#defineRUNNING0x1#defineRUNNABLE0x2#defineSTACK_SIZE8192#defineMAX_THREAD4*// Saved registers for kernel context switches.*struct context { uint64 ra; uint64 sp;*// callee-saved* uint64 s0; uint64 s1; uint64 s2; uint64 s3; uint64 s4; uint64 s5; uint64 s6; uint64 s7; uint64 s8; uint64 s9; uint64 s10; uint64 s11;};struct thread {char stack[STACK_SIZE]; */* the thread's stack */*int state; */* FREE, RUNNING, RUNNABLE */*struct context context; *// swtch() here to run process*};struct thread all_thread[MAX_THREAD];struct thread *current_thread;externvoidthread_switch(struct context*,struct context*);voidthread_init(void){*// main() is thread 0, which will make the first invocation to**// thread_schedule(). it needs a stack so that the first thread_switch() can**// save thread 0's state. thread_schedule() won't run the main thread ever**// again, because its state is set to RUNNING, and thread_schedule() selects**// a RUNNABLE thread.* current_thread =&all_thread[0];current_thread->state = RUNNING;}voidthread_schedule(void){struct thread *t,*next_thread;*/* Find another runnable thread. */* next_thread =0; t = current_thread +1;for(int i =0; i < MAX_THREAD; i++){if(t >= all_thread + MAX_THREAD) t = all_thread;if(t->state == RUNNABLE) { next_thread = t;break; } t = t +1; }if (next_thread ==0) {printf("thread_schedule: no runnable threads\n");exit(-1); }if (current_thread != next_thread) { */* switch threads? */*next_thread->state = RUNNING; t = current_thread; current_thread = next_thread;*/* YOUR CODE HERE*** Invoke thread_switch to switch from t to next_thread:***/*thread_switch(&t->context,¤t_thread->context); } else next_thread =0;}voidthread_create(void (*func)()){struct thread *t;for (t = all_thread; t < all_thread + MAX_THREAD; t++) {if (t->state == FREE) break; }t->state = RUNNABLE;*// YOUR CODE HERE*memset(&t->context,0,sizeoft->context);t->context.ra = (uint64)func;*/***In the standard RISC-V calling convention,**the stack grows downward and the stack pointer is**always kept 16-byte aligned.***/*t->context.sp = (uint64)(t->stack + STACK_SIZE); *// how does stack grow?*}voidthread_yield(void){current_thread->state = RUNNABLE;thread_schedule();}volatileint a_started, b_started, c_started;volatileint a_n, b_n, c_n;voidthread_a(void){int i;printf("thread_a started\n"); a_started =1;while(b_started ==0|| c_started ==0)thread_yield();for (i =0; i <100; i++) {printf("thread_a %d\n", i); a_n +=1;thread_yield(); }printf("thread_a: exit after %d\n", a_n);current_thread->state = FREE;thread_schedule();}voidthread_b(void){int i;printf("thread_b started\n"); b_started =1;while(a_started ==0|| c_started ==0)thread_yield();for (i =0; i <100; i++) {printf("thread_b %d\n", i); b_n +=1;thread_yield(); }printf("thread_b: exit after %d\n”, b_n); current_thread->state = FREE; thread_schedule();}void thread_c(void){ int i; printf(“thread_c started\n”); c_started = 1; while(a_started == 0 || b_started == 0) thread_yield(); for (i = 0; i < 100; i++) { printf(“thread_c %d\n”, i); c_n += 1; thread_yield(); } printf("thread_c: exit after %d\n", c_n); current_thread->state = FREE; thread_schedule();}int main(int *argc*, char **argv*[]) { a_started = b_started = c_started = 0; a_n = b_n = c_n = 0; thread_init(); thread_create(thread_a); thread_create(thread_b); thread_create(thread_c); thread_schedule(); exit(0);}