Libco协程库实现(二)

这里补充下libco后续对于协程间切换的汇编新实现,原来的实现方法之前分析过Libco协程库实现,早期分析的时候有一个地方写错。没有写具体debug信息及过程,应私信的网友要求,这里详细分析下整个过程并配上相关的数据。

这里先贴上老的实现x86_64:

 56 #elif defined(__x86_64__)
 57     leaq 8(%rsp),%rax
 58     leaq 112(%rdi),%rsp
 59     pushq %rax
 60     pushq %rbx
 61     pushq %rcx
 62     pushq %rdx
 63 
 64     pushq -8(%rax) //ret func addr
 65 
 66     pushq %rsi
 67     pushq %rdi
 68     pushq %rbp
 69     pushq %r8
 70     pushq %r9
 71     pushq %r12
 72     pushq %r13
 73     pushq %r14
 74     pushq %r15

 76     movq %rsi, %rsp
 77     popq %r15
 78     popq %r14
 79     popq %r13
 80     popq %r12
 81     popq %r9
 82     popq %r8
 83     popq %rbp
 84     popq %rdi
 85     popq %rsi
 86     popq %rax //ret func addr
 87     popq %rdx
 88     popq %rcx
 89     popq %rbx
 90     popq %rsp
 91     pushq %rax
 92 
 93     xorl %eax, %eax
 94     ret
 95 #endif

以上是保存要切出协程的寄存器上下文,和要切入协程的寄存器上下文,具体不再分析。

新的实现如下:

 48     leaq (%rsp),%rax
 49     movq %rax, 104(%rdi)
 50     movq %rbx, 96(%rdi)
 51     movq %rcx, 88(%rdi)
 52     movq %rdx, 80(%rdi)
 53       movq 0(%rax), %rax
 54       movq %rax, 72(%rdi)
 55     movq %rsi, 64(%rdi)
 56       movq %rdi, 56(%rdi)
 57     movq %rbp, 48(%rdi)
 58     movq %r8, 40(%rdi)
 59     movq %r9, 32(%rdi)
 60     movq %r12, 24(%rdi)
 61     movq %r13, 16(%rdi)
 62     movq %r14, 8(%rdi)
 63     movq %r15, (%rdi)
 64       xorq %rax, %rax
 65 
 66     movq 48(%rsi), %rbp
 67     movq 104(%rsi), %rsp
 68     movq (%rsi), %r15
 69     movq 8(%rsi), %r14
 70     movq 16(%rsi), %r13
 71     movq 24(%rsi), %r12
 72     movq 32(%rsi), %r9
 73     movq 40(%rsi), %r8
 74     movq 56(%rsi), %rdi
 75     movq 80(%rsi), %rdx
 76     movq 88(%rsi), %rcx
 77     movq 96(%rsi), %rbx
 78         leaq 8(%rsp), %rsp
 79         pushq 72(%rsi)
 80 
 81     movq 64(%rsi), %rsi
 82     ret

由于两者的区别从实现上看一个是pushq指令,一个是movq指令,而前者的实现大概如下:
pushq时,先将栈顶指针减8,再将值写到新栈顶地址,如:
   pushq %rbp
等价于:
   subq $8,%rsp
   movq %rbp,(%rsp)
这里因为知道要保存和恢复的寄存器,取哪个直接加上相对偏移量,可能这里为了省去subq这个步骤?

由于切换的时候并不算是真正的函数调用,所以汇编代码处并没有被调函数开始处的pushq %rbp的指令,最后返回函数的popq %rbp,在asm中手动保存这两个,保存和恢复的反汇编:

17079 coctx_swap:
17080 1000096c0:  48 8d 04 24     leaq    (%rsp), %rax
17081 1000096c4:  48 89 47 68     movq    %rax, 104(%rdi)//last rsp
17085 1000096d4:  48 8b 00    movq    (%rax), %rax
17086 1000096d7:  48 89 47 48     movq    %rax, 72(%rdi)//last rip
17089 1000096e3:  48 89 6f 30     movq    %rbp, 48(%rdi)//last rbp
17097 100009701:  48 8b 6e 30     movq    48(%rsi), %rbp//restore rbp
17098 100009705:  48 8b 66 68     movq    104(%rsi), %rsp//restore rsp
17109 100009730:  48 8d 64 24 08  leaq    8(%rsp), %rsp//skip old rip
17110 100009735:  ff 76 48    pushq   72(%rsi)//last rip
17112 10000973c:  c3  retq //jump last rip

这里说明下call和ret指令的作用,为后续的debug时作说明:
cpu执行call跳转指令时,cpu做了如下操作:
   rsp = rsp-8
   rsp = rip
//即跳转之前会将下一条要执行语句指令地址压入栈顶,call等同于以下两条语句,但call本身就一条指令
   pushq %rip
   jmp 标号
类似ret指令会将栈顶的内容弹出到rip寄存器中,继续执行:
   rip = rsp
   rsp = rsp+8
//等同于
   pop %rip

这里以example_copystack.cpp为例说明整个切换过程,其中在coctx_make/co_swap/coctx_swap打断点,因为算上主协程,一共有三个协程。
0号协程执行完coctx_make后的内容快照为:

(gdb) p ctx
$1 = (coctx_t *) 0x100805018
(gdb) p *ctx
$2 = {regs = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100805000, 0x0, 
    0x100001f70 <CoRoutineFunc(stCoRoutine_t*, void*)>, 0x0, 0x0, 0x0, 0x10031fff0}, 
  ss_size = 131072, ss_sp = 0x100300000 ""}
(gdb) p &ctx->regs
$3 = (void *(*)[14]) 0x100805018

当执行到co_swap时,因为就主协程和0号协程:

(gdb) n
co_swap (curr=0x7ffeefbff9a8, pending_co=0x100805000) at co_routine.cpp:637
637     stCoRoutineEnv_t* env = co_get_curr_thread_env();
(gdb) n
641     curr->stack_sp= &c;
(gdb) p &pending_co->ctx
$4 = (coctx_t *) 0x100805018

接着进入coctx_swap,这里跳过切出主协程的快照,只看切入0号协程的内容,执行:

   0x00000001000058bd <+62>:    xor    %rax,%rax
=> 0x00000001000058c0 <+65>:    mov    0x30(%rsi),%rbp
   0x00000001000058c4 <+69>:    mov    0x68(%rsi),%rsp
   0x00000001000058c8 <+73>:    mov    (%rsi),%r15
   0x00000001000058cb <+76>:    mov    0x8(%rsi),%r14
   0x00000001000058cf <+80>:    mov    0x10(%rsi),%r13
   0x00000001000058d3 <+84>:    mov    0x18(%rsi),%r12
   0x00000001000058d7 <+88>:    mov    0x20(%rsi),%r9
   0x00000001000058db <+92>:    mov    0x28(%rsi),%r8
   0x00000001000058df <+96>:    mov    0x38(%rsi),%rdi
   0x00000001000058e3 <+100>:   mov    0x50(%rsi),%rdx
   0x00000001000058e7 <+104>:   mov    0x58(%rsi),%rcx
   0x00000001000058eb <+108>:   mov    0x60(%rsi),%rbx
   0x00000001000058ef <+112>:   lea    0x8(%rsp),%rsp
   0x00000001000058f4 <+117>:   pushq  0x48(%rsi)
   0x00000001000058f7 <+120>:   mov    0x40(%rsi),%rsi
   0x00000001000058fb <+124>:   retq 

以下是分别执行每一条汇编时各寄存器的内容:

(gdb) i r rsi
rsi            0x100805018        

此时rsi指向的是&pending_co->ctxmov 0x30(%rsi),%rbp是把pending_co->ctx->regs[6]中的内容mov到rbp,即rbp;

0x00000001000058c8 in coctx_swap ()
(gdb) i r rsp
rsp            0x10031fff0         0x10031fff0

把pending_co->ctx->regs[13]中的内容mov到rsp,即rsp:

111   char* sp = ctx->ss_sp + ctx->ss_size - sizeof(void*);
112   sp = (char*)((unsigned long)sp & -16LL);
113   
114   memset(ctx->regs, 0, sizeof(ctx->regs));
115   void** ret_addr = (void**)(sp);
116   *ret_addr = (void*)pfn;
117   
118   ctx->regs[13] = sp;

后面的汇编代码实现的效果是把pending_co->ctx->regs[0]恢复到r15,

0x00000001000058e3 in coctx_swap ()
(gdb) i r rdi
rdi            0x100805000         4303376384

0x00000001000058f4 in coctx_swap ()
(gdb) i r rsp
rsp            0x10031fff8         0x10031fff8

当执行到时这四行时:

   0x00000001000058ef <+112>:   lea    0x8(%rsp),%rsp
   0x00000001000058f4 <+117>:   pushq  0x48(%rsi)
=> 0x00000001000058f7 <+120>:   mov    0x40(%rsi),%rsi
   0x00000001000058fb <+124>:   retq

此时后续的各寄存器内容为:

(gdb) x/2ag 0x10031fff0
0x10031fff0:    0x100001f70 <_ZL13CoRoutineFuncP13stCoRoutine_tPv>  0x0

0x0000000100001f70 in CoRoutineFunc(stCoRoutine_t*, void*) () at co_routine.cpp:568
568     co_swap( lpCurrRoutine, co );
(gdb) i r rip
rip            0x100001f70         0x100001f70 <CoRoutineFunc(stCoRoutine_t*, void*)>

整个切入到pending_co协程已经完成。

切出是个相反的过程,这里不再debug具体过程,其中:

   0x00000001000096c0 <+0>: lea    (%rsp),%rax
   0x00000001000096c4 <+4>: mov    %rax,0x68(%rdi)
   0x00000001000096c8 <+8>: mov    %rbx,0x60(%rdi)
   0x00000001000096cc <+12>:    mov    %rcx,0x58(%rdi)
   0x00000001000096d0 <+16>:    mov    %rdx,0x50(%rdi)
=> 0x00000001000096d4 <+20>:    mov    (%rax),%rax
   0x00000001000096d7 <+23>:    mov    %rax,0x48(%rdi)
   0x00000001000096db <+27>:    mov    %rsi,0x40(%rdi)
   0x00000001000096df <+31>:    mov    %rdi,0x38(%rdi)
   0x00000001000096e3 <+35>:    mov    %rbp,0x30(%rdi)
   0x00000001000096e7 <+39>:    mov    %r8,0x28(%rdi)
   0x00000001000096eb <+43>:    mov    %r9,0x20(%rdi)
   0x00000001000096ef <+47>:    mov    %r12,0x18(%rdi)
   0x00000001000096f3 <+51>:    mov    %r13,0x10(%rdi)
   0x00000001000096f7 <+55>:    mov    %r14,0x8(%rdi)
   0x00000001000096fb <+59>:    mov    %r15,(%rdi)

把返回地址保存到ctx->regs[9]:

0x00000001000096d7 in coctx_swap ()
(gdb) i r rax
rax            0x100002c70         4294978672
 1516 100002c6b:  e8 50 6a 00 00  callq   27216 <coctx_swap>
 1517 100002c70:  e8 1b fc ff ff  callq   -997 <__Z22co_get_curr_thread_envv>

后续有时间分析下lua中协程的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容