【协程原理】 - 协程不过是用户态的线程

TL;DR数据结构

笔者最美好的记忆来自于早年在6502 cpu的cc800上写汇编的年代, 那个时代的计算机甚至没有操做系统,也没有实模式等保护机制。在6502上写汇编应用其实很是简单,系统会把bin文件加载到一个固定的内存地址中,cpu会固定地从一个特定的位置开始执行。而后cpu就按照你提供的机器指令开始一条一条的执行。在高级语言中的“函数调用”的概念,在汇编里主要体现为两个寄存器。寄存器是cpu内部临时保存数据的区域,至关于高级语言里的变量。可是有一个寄存器是特殊的,它存放了cpu当前正在执行的指令的内存地址(Instruction Register)。一个高级语言中的函数通常会被编译成指令存放在一段连续的内存空间中(data segment)。那么所谓函数执行到了第几行这样的信息其实就是保存在这个Instruction Register中的。另一个很特殊的寄存器是Stack Register,它其中存放的内存地址指向的内存区域用于函数之间传递参数和返回值,以及存放一个函数内的局部变量。若是不考虑现代计算机cpu中各类各样其余存放中间结果的寄存器,理论上保存了Instruction Register(执行到哪儿了)和Stack Register(堆栈上的变量)就保存了一个函数的当前执行状态,分别是函数当前执行到了哪,以及这个函数局部变量所表明的当前state。函数

事实上,操做系统的几个关键切换也是这么来完成的。操做系统提供了两个执行态,一个是用户态,通常咱们的代码都是执行在用户态的。另一个是内核态,像驱动程序之类的代码会用各类方式被加载到操做系统内部执行在内核之中。内核态里的代码能够彻底控制CPU的I/O中断,从而能够和外部设备交互。用户态的代码属于受限代码,必须把I/O请求经过syscall交由运行在内核态的操做系统来完成。当一个cpu的核在执行用户态代码时,其寄存器里存放的状态是你的应用的代码的状态,可是应用要进行I/O操做的时候,cpu要被切换到内核的代码里去执行内核态的代码。这里就须要进行一次context switch,所谓context switch其实原理不会比把寄存器的值存到内存的一个地方,等回来的时候再把内存中临时保存的值加载回寄存器复杂多少。操作系统

操做系统还有一个须要进行context switch的地方,那就是在协程与协程之间。操做系统在执行一个ELF或者PE的可执行文件的时候,对于这个可执行文件内的汇编代码来讲,整个内存寻址空间是独立的。也就是1.exe的执行状态彻底没法感知到2.exe的执行状态的内存。也就是现代操做系统的虚拟内存空间。有cpu在两个进程之间切换状态的时候,须要把内存的映射关系调整过来,不然虚拟内存的地址是没法对应到正确的物理地址的。一个进程内的两个线成切换的时候,要稍微简单一些,只须要把当前线成正在执行的位置和栈作切换就能够了。线程

不管是操做系统作user/kernel的switch,仍是process/process,thread/thread的switch,其实现方式都是大同小异的。经过把“当前执行状态”这样的一个抽象概念落实为一个具体的数据结构存储起来,而后指挥cpu在不一样的场合加载不一样的数据恢复不一样的“当前执行状态”。设计

在高级语言中,一个函数正在执行的位置以及其状态,内部均可以有一个抽象的表达方式。有的高级语言直接被编译成原生的机器码,那么其执行状态的表述就和操做系统的context switch的context很是相似。有的高级语言自身执行在一个虚拟机之上,那么其context的表述多是虚拟机的instruction register和stack register,而不是80x86这样原生的机器的物理寄存器。可是原理是很是相似的。协程

取决于语言设计者的觉悟,有的语言会把这种表达执行状态的能力直接提供出来,让一个函数在执行过程当中能够把当前状态保存,而后把执行权交给另一个函数执行,等那个函数放弃执行权回来的时候再把保存的状态恢复。这也就是所谓的协程(co-routine)。协程与线程的区别在于,协程的context switch是在彻底在用户态,由语言的runtime或者是库来完成的。而线程的context switch则是操做系统来完成的。进程