libgo源码剖析(3。libgo上下文切换实现)

  

在libgo的上下文切换上,并没有自己去实现创建和维护栈空间,保存和切换CPU寄存器执行状态信息等的任务,而是直接使用了Boost.Context.Boost。上下文作为众多协程底层支持库、性能方面一直在被优化。

  
  

Boost.Context所做的工作,就是在传统的线程环境中可以保存当前执行的抽象状态信息(栈空间,栈指针,CPU寄存器和状态寄存器,IP指令指针),然后暂停当前的执行状态,程序的执行流程跳转到其他位置继续执行,这个基础构建可以用于开辟用户态的线程,从而构建出更加高级的协程等操作接口。同时因为这个切换是在用户空间的,所以资源损耗很小,同时保存了栈空间和执行状态的所有信息,所以其中的函数可以自由被嵌套使用。
引用自https://yq.aliyun.com/ziliao/43404

     

1。fcontext_t

  
  

libgo/背景/fcontext.h

     

增加。上下文的底层实现是通过fcontext_t结构体来保存协程状态,使用make_fcontext创建协程,使用jump_fcontext实现协程切换。在libgo协程中,直接引用了这两个接口函数.boost的内部实现这里不讨论,感兴趣的话可以在上面连接中查看。

  
 <代码>//所有内容和提升。环境中的声明一致
  外来的“C”
  {
  typedef void * fcontext_t;
  typedef无效(* fn_t) (intptr_t);/*
  *从离岸金融中心切换到nfc的上下文
  * */intptr_t jump_fcontext (fcontext_t *离岸金融中心,fcontext_t nfc intptr_t vp, bool preserve_fpu=false);/*
  *创建上下问对象
  * */fcontext_t make_fcontext (void *堆栈,std:: size_t大小,fn_t fn);
  } 
  

除此之外,还提供了一系列的栈函数

  
 <代码> struct StackTraits
  {
  静态stack_malloc_fn_t&MallocFunc ();
  
  静态stack_free_fn_t&FreeFunc ();//获取当前栈顶设置的保护页的页数
  静态int,GetProtectStackPageSize ();//对保护页的内容做保护
  静态bool ProtectStack (void *栈,std:: size_t大小,int页大小);//取消对保护页的内存保护,析构是才会调用
  静态孔隙UnprotectStack (void *栈,int页大小);
  }; 
  

当用户去管理协程栈当时候,稍不注意,就会出现访问栈越界当问题。只读操作还好,但是如果进行了写操作,整个程序就会直接奔溃,因此,栈保护工作还是十分必要的。

  

栈保护

  

libgo对栈对保护,使用了mprotect系统调用实现。我们在给该协程创建了大小为N字节对栈空间时,会对栈顶的一部分的空间进行保护,因此,分配的协程栈的大小,应该要大于要保护的内存页数加一。

  
  

为什么提到保护栈,总是以页为单位呢?因为mprotect是按照页来进行设置的,因此,对没有对其对地址,应该首先对其之后再去操作。

     
 <代码> bool StackTraits: ProtectStack (void *栈,std:: size_t大小,int页大小)
  {//协程栈的大小,应该大于(保护内存页数+ 1)
  如果(! pageSize)返回false;
  如果((int)大小& lt;=getpagesize() *(页大小+ 1))
  返回错误;//使用mprotect保护的内存页应该是按页对其的//栈从高地址向地地址生长,被保护的栈空间应该位于栈顶(低地址处)//protect_page_addr是在当前协程栈内取最近的整数页边界的地址,如:0 xf7234008——→0 xf7235000
  void * protect_page_addr=((std:: size_t)堆栈,0 xfff) ?(void *) (((std:: size_t)堆栈,~ (std:: size_t) 0 xfff) + 0 x1000):堆栈;//使用mprotect系统调用实现栈保护,PROT_NONE表明该内存空间不可访问
  如果(1==mprotect (protect_page_addr getpagesize() *页大小,PROT_NONE)) {
  DebugPrint (dbg_task“origin_addr: % p, align_addr: % p,页大小:% d, protect_page: % u,保护堆栈栈错误:% s”,堆栈,protect_page_addr, getpagesize(),页大小,strerror (errno));
  返回错误;
  其他}{
  DebugPrint (dbg_task“origin_addr: % p, align_addr: % p,页大小:% d, protect_page: % u,保护堆栈成功。”,
  堆栈、protect_page_addr页大小,getpagesize ());
  返回true;
  }
  } 
  

取消栈保护

  

取消栈保护只有在释放该协程空间的时候会调用。

  
 <代码>空白StackTraits: UnprotectStack (void *栈,int页大小)
  {
  如果(! pageSize)返回;
  
  void * protect_page_addr=((std:: size_t)堆栈,0 xfff) ?(void *) (((std:: size_t)堆栈,~ (std:: size_t) 0 xfff) + 0 x1000):堆栈;//允许该块内存可读可写
  如果(1==mprotect (protect_page_addr getpagesize() *页大小,PROT_READ | PROT_WRITE分别)){
  DebugPrint (dbg_task“origin_addr: % p, align_addr: % p,页大小:% d, protect_page: % u,保护堆栈栈错误:% s”,堆栈,protect_page_addr, getpagesize(),页大小,strerror (errno));
  其他}{
  DebugPrint (dbg_task“origin_addr: % p, align_addr: % p,页大小:% d, protect_page: % u,保护堆栈成功。”、堆栈、protect_page_addr页大小,getpagesize ());
  }
  }

libgo源码剖析(3。libgo上下文切换实现)