libco源码分析

最近接触到了libco,这是tencent开源的一个协程库,可以很方便的在c++中使用协程。而且封装了systemcall和epoll。比较好奇上下文切换相关的内容,就去看了看。库设计的还是很精巧的,相对于其它的c++的协程库,这个库同时考虑了协程的内存消耗和协程切换的平衡(添加了一个数量可调的共享的栈列表,部分情况拷贝栈,部分情况不用拷贝(刚好和上一个一样的协程的时候))

给源码fork了一份,给几个地方加了点注释:https://github.com/zhjzhjxzhl/libco 

主要数据结构:

1、coctx_t  协程上下文,包括通用寄存器,和使用的栈的内容存储

2、stStackMem 协程运行的栈,主协程是系统栈,其它协程都是在这个栈列表里运行的,是一个取模来决定用哪个栈的,多个协程可以对应一个栈,这样主要是为了节省内存,开更多协程,还有就是协程的上下文栈复制,也就是memcopy的一个开销。而且都是在切换的时候,才决定要不要拷贝的,也可能出现还是原来协程的情况。

3、stTimeOut    封装中对于read,write等函数,做了一个timeout的支持,这个由主协程来负责时间管理,如果过期或者收到读写事件,都会返回到协程去执行的。

协程的切换,都放到了coctx_swap.S文件里,这里主要是利用了x86函数调用栈时返回地址处于栈顶,且ret指令,会去执行当前栈顶的地址所指向的指令,这个特性来做的切换。具体的注释如下(只注释了x86 32位的)

.globl coctx_swap
 #if !defined( __APPLE__ ) && !defined( __FreeBSD__ )
 .type coctx_swap, @function
 #endif
 coctx_swap:
  
 #if defined(__i386__)
  //此时上一个函数的压栈已经完成了,当前栈结构是 sp->返回地址 sp+4->参数1的地址,也就是当前协程 sp+8参数2的地址,也就是目标协程
  //因为x86的push esp,mov ebp=esp是加在下一个函数里的,所以此处还没有执行。
  leal 4(%esp), %eax //参数一,也就是当前协程地址放到eax
  movl 4(%esp), %esp // 参数一的地址放进esp,因为这个结构,刚好是要切换出去的协程的的寄存器缓存地址
  leal 32(%esp), %esp //parm a : &regs[7] + sizeof(void*) //给寄存器的地址+8,因为栈只能从高往低操作,而堆是从低往高的,所以挪到末尾,因为push操作会减sp的值
  
  pushl %eax //esp ->parm a //参数一地址压栈,主要是为了保存住sp+4的地址
  
  pushl %ebp
  pushl %esi
  pushl %edi
  pushl %edx
  pushl %ecx
  pushl %ebx
  pushl -4(%eax) //缓存的返回地址压栈
  
 
  movl 4(%eax), %esp //parm b -> &regs[0] //相当于sp+8,就是目标协程的地址放到eax
  
  popl %eax //ret func addr //最后压入的是返回地址
  popl %ebx
  popl %ecx
  popl %edx
  popl %edi
  popl %esi
  popl %ebp
  popl %esp //前一个栈的参数1地址,也就是sp+的地址,下面在push %eax,那么sp刚好回到原位,而且返回地址刚好处于当前的栈顶。x86 ret函数之前,会插入sp=bp,bp=pop
  pushl %eax //set ret func addr 下面在push %eax,那么sp刚好回到原位,而且返回地址刚好处于当前的栈顶。x86 ret函数之前,会插入sp=bp,bp=pop
  //反汇编的的时候,这里看到的是leaveq,就是这个意思
  
  xorl %eax, %eax
  ret //现在栈顶是返回地址,所有的寄存器都恢复了,sp也指向了共享栈的位置,ret操作,直接执行栈顶指向的代码,就ok了。

协程初始化的部分,在coctx.cpp 的 coctx_make函数中,在代码执行先,先给esp和eip赋值好,

#if defined(__i386__)
 int coctx_init( coctx_t *ctx )
 {
 memset( ctx,0,sizeof(*ctx));
 return 0;
 }
 int coctx_make( coctx_t *ctx,coctx_pfn_t pfn,const void *s,const void *s1 )
 {
 //make room for coctx_param
 char *sp = ctx->ss_sp + ctx->ss_size - sizeof(coctx_param_t); //将sp对到栈顶,然后留出参数的位置,8个字节
 sp = (char*)((unsigned long)sp & -16L); //这一步是处理内存对齐的问题,猜测
  
  
 coctx_param_t* param = (coctx_param_t*)sp ; //将参数放到栈顶,之后函数调用方便
 param->s1 = s;
 param->s2 = s1;
  
 memset(ctx->regs, 0, sizeof(ctx->regs));
  
 ctx->regs[ kESP ] = (char*)(sp) - sizeof(void*); //sp往下移动,为返回地址预留空间。
 ctx->regs[ kEIP ] = (char*)pfn; //对应的下一条指令的位置,也就是函数指针的位置
  
 return 0;
 }

那么在栈运行的时候,就可以切换到自己定义的栈上来,而eip指向的是 co_rountine.cpp中的CoRoutingFunc中,而其中的co->pfn()中会调用一些导致协程切换的函数。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值