在libgo的上下文切换上,并没有自己去实现创建和维护栈空间,保存和切换CPU寄存器执行状态信息等的任务,而是直接使用了Boost.Context.Boost。上下文作为众多协程底层支持库、性能方面一直在被优化。
Boost.Context所做的工作,就是在传统的线程环境中可以保存当前执行的抽象状态信息(栈空间,栈指针,CPU寄存器和状态寄存器,IP指令指针),然后暂停当前的执行状态,程序的执行流程跳转到其他位置继续执行,这个基础构建可以用于开辟用户态的线程,从而构建出更加高级的协程等操作接口。同时因为这个切换是在用户空间的,所以资源损耗很小,同时保存了栈空间和执行状态的所有信息,所以其中的函数可以自由被嵌套使用。
引用>
引用自https://yq.aliyun.com/ziliao/434041。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上下文切换实现)