协程介绍
与子例程一样,协程也是一种程序组件。 相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。 协程源自Simula和Modula-2语言,但也有其他语言支持。 协程更适合于用来实现彼此熟悉的程序组件,如合作式多任务,迭代器,无限列表和管道。 –维基百科
下面我们会以Libtask(Go语言的作者之一Russ Cox的作品)作为分析案例来解释协程的原理。
协程工作原理
要了解协程的工作原理,可以从以下几点入手:
1、上下文切换。
2、函数调用原理。
3、Libtask保存寄存器值的结构体。
4、Libtask的接口函数和实现。
1、上下文切换
当一个程序被执行(称为进程)的时候,这些寄存器的值通常会被修改。所以当要切换进程执行的时候,只需要把这些寄存器的值保存下来,然后把新进程寄存器的值赋值到CPU中(我们知道CPU的使命就是执行程序中的指令,而且CPU内部有很多用于存放数据的寄存器,其中比较重要的一个寄存器叫EIP寄存器,它用于存储下一条要执行的指令。除了EIP寄存器之外,还有一个比较重要的寄存器叫ESP寄存器,它用于保存程序的栈顶位置。除此之外,CPU还有很多其他用途的寄存器,如:通用寄存器EAX、EDX和段寄存器CS、DS等等。),那么就完成进程切换了,通常我们把这个过程称为上下文切换,协程的切换也类似。
2、函数的调用原理
以C语言为例,函数调用时通过栈结构来保存现场和恢复现场的。比如,在第189行有这样一段代码来进行函数调用:demoFunc(a, b, c, d);
那么,第190行代码的地址会被放在栈底,然后,实参从右往左一次入栈。这样一来,当该函数完毕,程序又会恢复到之前调用处(189行)的下一行(190行)。原理如下图(图片来自百度):
3、Libtask保存寄存器值的结构体
前面说过,要进行上下文切换,存储对应寄存器的值是必不可少的。Libtask通过引进struct mcontext
这个结构体来保存对应的寄存器的值,以下是struct mcontext
这个结构体的源码实现,命名比较规范,感兴趣的读者