这个实验总体来说难度很小,第一个实验是在 xv6 中实现一个简单的协程,然后之后的两个实验均只需要简单地使用多线程的锁,并且在自己的电脑上完成。
Uthread: switching between threads
这个实验主要涉及到用户程序 uthread.c。
观察全局变量,可以看到多个 thread 的信息直接在此声明,包括状态、栈空间等。
thread_switch 函数用来切换上下文,将在 thread_schedule 中用到。
thread_abc 的函数通过调用 thread_yield 来释放 cpu,然后进入调度器,然后上下文切换到另一个 thread。
可以看出 xv6 的这个 thread 根本不是操作系统学过的或者 linux 中熟悉的那种线程,后者可以由操作系统调度,前者对操作系统而言根本上就是一个进程,并且相互之间还不能并行。不过 thread_a 和 thread_b 之间是存在并发的,虽然本质上还是串行执行。
所需要完成的函数包括 thread_create,thread_switch,thread_schedule。
我的解法中,还是先创建了一个结构体,来记录需要保存和恢复的寄存器的值,然后直接放进 struct thread 中。这比放在 thread.stack[some where] 方便多了。
所需要保存的寄存器仅仅只是被调用者保存寄存器,原因如下:
某个 thread 在执行过程中,调用了 thread_yield,然后就会进入 thread_schedule,这个时候 sp 还是指向 thread.stack[some where] 的,是主进程的某个放置全局变量的地方,不是主进程的栈,也跟其他所谓 thread 的栈毫无关系。
然后进行切换,schedular 会调用 thread_switch 来切换上下文,这个时候 sp 被切换了,此时栈指针已经指向了另一个 thread 的所谓的栈上。那么反过来想象,如果是第二个 thread 切换回第一个 thread,对于第一个 thread 的执行来说,完全等同与“thread_switch”函数被调用后什么都没有做直接返回。那么实际上原先调用者保存的寄存器都被调用者所管理,会在栈上该恢复的进行恢复,此处切换仅仅需要恢复被调用者保存寄存器即可。
Using threads
多线程读写哈希表。原本没有加锁,那必然出现问题,不能满足一致性。加锁即可解决。
但是也不能乱加锁,如果加一个全局锁,那就变成串行了,还是得利用好多线程资源。因此使用更细粒度的锁,对于哈希表每个 bucket 都分配一个锁,从而减少竞争。
Barrier
熟悉睡眠锁,即 pthread wait 和 broadcast 的使用。
线程走到 barrier 的时候就进入休眠,直到最后一个走到 barrier 的线程也运行到该处,就一同唤醒。