在实现了 xv6 的用户态协程后, 知道了协作式调度的底层原理,双方通过主动让出的方式释放执行权,并在让出的时候保存上下文寄存器,修改为目标协程的寄存器, 每个协程也都有自己的执行栈, 在 xv6 中保存在静态区的固定 4K 的栈

以下是在学习调度过程中遇到的一些问题和找到的解答

问题 1: go 的调度也是这样实现的吗?

go 在 1.14 之前都是协作式的调度,难怪被叫做协程,后来才改为了基于信号的抢占式调度

在 1.14 之前是通过在代码中插入调度检查点来做到主动让出的, 像死循环这样的代码也会在循环内插入检查点来实现,但是性能损耗很高

在 1.14 后实现的抢占式调度,是基于信号的, 信号就类似内核里的硬件中断,可以强行把执行权转交给某个中断处理程序

问题2: 在信号处理程序里要如何拿到对应协程的寄存器值

到这里看起来好像很简单,也就和内核类似,保存寄存器,还原寄存器. 但是还有一个地方不明白的是, 在信号处理程序里要如何拿到对应协程的寄存器值,毕竟当前的寄存器值已经是信号处理程序的值了

查阅信号相关的系统调用也没找到和寄存器相关的参数 , 如果系统调用提供了修改和获取的参数那就最好了 , 但是没有

后来查阅资料得知, linux 在进入信号处理程序后, 会在栈中保存被中断的程序的寄存器值,sigcontext , 并且在还原的时候会从这里取, 这样就解决了寄存器获取和修改的问题

Paste_Image.png

问题 3:为什么没有在信号处理程序里做调度?

这个问题在查看 gophercon 的一个视频后得到了解答: 在信号处理程序中处理调度会引发很多问题 , 于是作者把调度移到了正常状态下的程序运行

问题 4:为什么只能在安全状态下运行调度

因为一个指针操作可能由多个机器指令构成, 被抢占的时候可能只执行了部分指令 , 此时指针的状态难以判断处是否可以回收 , 因此 go 的做法是在抢占的时候还要判断是否能安全抢占 , 像处在垃圾回收时就是不安全状态

参考资料