用户态线程作为轻量级的进程,相比于进程有着更加方便的通信机制和更加灵活的使用 方法。本次实验的主题是用户态线程,我们将主要在用户态进行线程相关的实验。
首先切换到 thread 分支
在本实验中,我们需要完成一个用户态线程库中的功能。xv6 为该实验提供了基本的代码: user/uthread.c 和 user/uthread_switch.S 。我们需要在 user/uthread.c 中实现 thread_create() 和 thread_schedule() ,并且在 user/uthread_switch.S 中实现 thread_switch用于切换上下文。
首先我们查看 user/uthread.c 中关于线程的一些数据结构:
/* Possible states of a thread: */
#define FREE 0x0
#define RUNNING 0x1
#define RUNNABLE 0x2
#define STACK_SIZE 8192
#define MAX_THREAD 4
struct thread {
char stack[STACK_SIZE]; /* the thread's stack */
int state; /* FREE, RUNNING, RUNNABLE */
};
struct thread all_thread[MAX_THREAD];
struct thread *current_thread;
线程的数据结构十分简洁,struct thread 中,一个字节数组用作线程的栈,一个整数用于表示线程的状态。不难发现我们还需要增加一个数据结构用于保存每个线程的上下文,故参照内核中关于进程上下文的代码,增加以下内容并把它加到上述 thread结构体中:
// Saved registers for user 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;
};
参考 xv6 实验手册的提示,除了 sp 、s0 和 ra 寄存器,我们只需要保存 callee-saved 寄存器,因此构造了上面的 struct context 结构体。有了该结构体,我们仿照 kernel/trampoline.S的结构,按照 struct context 各项在内存中的位置,在 user/uthread_switch.S 中加入如下的代码:
thread_switch:
/* YOUR CODE HERE */
/* Save registers */
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)
/* Restore registers */
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 */
以上函数的实现过程如下:
这样就完成了上下文切换的功能,接下来需要完成创建线程和调度线程的部分。创建线程时,我们需要将线程的栈设置好,并且需要保证在线程被调度运行时能够将 pc 跳转到正确的位置。上面的 thread_switch 在保存第一个进程的上下文后会加载第二个进程的上下文,然后跳至刚刚加载的 ra 地址处开始执行,故而我们在创建进程时只需将 ra 设为我们所要执行的线程的函数地址即可。于是 thread_create() 的实现如下:
.....
// YOUR CODE HERE
t->context.ra = (uint64)func;
t->context.sp = (uint64)&t->stack[STACK_SIZE]
类似的,在调度线程时,我们选中下一个可运行的线程后,使用 thread_switch 切换上下文即可,实现如下:
void
thread_schedule(void)
{
......
/* YOUR CODE HERE
* Invoke thread_switch to switch from t to next_thread:
* thread_switch(??, ??);
*/
// t->state = RUNNABLE;
thread_switch((uint64)&t->context, (uint64)¤t_thread->context);
......
}