开始日期:22.5.3
操作系统:Ubuntu20.0.4
Link:Lab Multithreading
my github repository: duilec/MITS6.081-fall2021/tree/thread
Lab Multithreading
写在前面
多线程这一部分以课程为主会好很多,教材的解释太繁琐了。
参考链接:Lab Multithreading
Uthread: switching between threads
实现用户态的线程切换,同内核态的线程切换比较,步骤减少了许多,不用多考虑陷阱帧(trapframe)的切换问题。只需要考虑栈指针、返回地址以及被调用者寄存器(callee register)。记得这是在一个进程内创建多个线程,所以contxt
是自定义的,并没有实际涉及到硬件。
这里实际上涉及到了用户级线程(ULT)的概念,这些线程由用户进程直接管理和调度,与内核无关。
整个过程:第一次调用thread_schedule();
之后,会离开thread[0]
(也即是进程main
),之后就一直在三个线程thread[1] ~ thread[3]
之间一直互相切换,直到exit(0)
。
Makefile
插入_uthread
1 | UPROGS=\ |
线程切换时需要保存/恢复被调用者寄存器,参照swthc.S
即可
- 注意
context
的定义必须在thread
之前,否则会报错incomplete type is not allowed
1 | /* we must defind it before defining thread */ |
1 | /* uthread_switch.S */ |
参考kernel/proc.c/allocproc()
,使线程恢复被调用者寄存器之后,能够直接跳到函数头部,同时,栈恢复为对应线程的栈。
uthread.c
中可以看到:*it needs a stack so that the first thread_switch() can save thread 0’s state.*,说明stack
是用来存放state
的,但笔者没有搞清楚是怎么存放的。笔者搞情况怎么存放的了,
Swtch(kernel/swtch.S:3)
saves only callee-saved registers; the C compiler generates code in the caller to save caller-saved registers on the stack.也就是说线程切换时,C编译器会把caller-saved registers存放在当前线程的stack中,而state是存放在一个caller-saved register当中的。
1 | void |
从当前线程的callee register
切换到下一个线程的callee register
1 | void |
接下来的实验都是在ubuntu上做的了,实验文件也都在文件夹notxv6
中,需要用到linux的不少接口,不过,都大同小异。
Using threads
在ph.c
前部声明一个全局锁
1 | ... |
在main()
中把锁给初始化
1 | int |
修改put()
- 第一对锁(先上锁再解锁)是防止双线程产生竞争(同时修改
key
和value
) - 第二对锁(先解锁再上锁)是用来加速
put()
的,因为使用第二对锁的这段代码不需要修改key
或者value
,只是访问了key
,而不是修改
1 | static |
Barrier
整体思路是:每一轮都有同样数目的线程来到,我们会阻塞所有线程,直到这一轮的最后一个线程进来,这时,我们将这一轮除最后一个线程外的所有线程唤醒,然后进入下一轮。
进入下一轮之前记得将
bstate.nthread = 0
,使得下一轮能够重新计数执行
pthread_cond_wait()
之后也需要解锁,否则会产生死锁go to sleep on cond, releasing lock mutex, acquiring upon wake up
进入睡眠之后,在
pthread_cond_wait()
中线程会释放锁,当线程被唤醒后会立刻获得锁,如果进入下一轮前没释放掉当前这一轮的锁,在下一轮就会一直请求锁,产生死锁
1 | static void |
./grade-lab-thread
1 | duile@LAPTOP-II21PK0U:xv6-labs-2021$ ./grade-lab-thread |
总结
- 完成日期:2022.5.4
- 第一个实验想到了要跳转到
func()
,但忘记了是给context.ra
使用函数地址func
,在切换完context
之后,以ret
的方式跳转到func()
- 第三个实验没考虑到
else
的使用,如果没有else
,if
之后仍会执行pthread_cond_wait()
,导致最后一个线程睡眠。 - 这是做实验以来最快完成的一次,代码量很少
- 最近听音乐都是用随机模式,下意识以为的一首歌竟然不是,很有意思