libco源代码解析之coctx_swap.S

赵星宇

libco是一个协程库,将少量的操作系统线程分解为大量的用户态协程。

用户态协程切换的时候需要切换上下文,这里会分析上下文切换的具体流程和代码。

本文以i386指令集为例进行分析。

coctx_swap.S文件中仅有一个函数:coctx_swap。

https://github.com/Tencent/libco/blob/master/coctx_swap.S

其被调用的方式为:coctx_swap(&(curr->ctx), &(pending_co->ctx));。

即传入当前和下一个两个上下文结构体的地址。

首先给出上下文结构体:

struct coctx_t

{

        void *regs[8];

        …

};

其中有几个成员,我们需要用到的只有regs指针数组。

下面列出coctx_swap函数的具体代码和每一时刻的内存布局:

红色的是代码

蓝色的是修改过的值

紫色的是这一步修改的值

coctx_swap:

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针

指向协程1的call_swap函数调用下一条指令的指针 <- esp

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        leal 4(%esp), %eax //sp

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针 <- esp

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        movl 4(%esp), %esp

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0 <- esp

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        leal 32(%esp), %esp //parm a : &regs[7] + sizeof(void*)

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

<- esp

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        pushl %eax //esp ->parm a

        pushl %ebp

        pushl %esi

        pushl %edi

        pushl %edx

        pushl %ecx

        pushl %ebx

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx <- esp

reg0

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        pushl -4(%eax)

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx

reg0 = 指向协程1的call_swap函数调用下一条指令的指针 <- esp

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0

-----低地址-----

        

        movl 4(%eax), %esp //parm b -> &regs[0]

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx

reg0 = 指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文2:

-----高地址-----

reg7

reg6

reg5

reg4

reg3

reg2

reg1

reg0 <- esp

-----低地址-----

---------------

新的寄存器epoch 寄存器名都是xxx1

---------------

        popl %eax  //ret func addr

        popl %ebx  

        popl %ecx

        popl %edx

        popl %edi

        popl %esi

        popl %ebp

        popl %esp

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针 <-esp1

指向协程2的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx

reg0 = 指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文2:

-----高地址-----

reg7 = esp1

reg6 = ebp1

reg5 = esi1

reg4 = edi1

reg3 = edx1

reg2 = ecx1

reg1 = ebx1

reg0 = 指向协程2的call_swap函数调用下一条指令的指针 = eax1

-----低地址-----

        pushl %eax //set ret func addr

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针

指向协程2的call_swap函数调用下一条指令的指针 = eax1 <- esp

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx

reg0 = 指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文2:

-----高地址-----

reg7 = esp1

reg6 = ebp1

reg5 = esi1

reg4 = edi1

reg3 = edx1

reg2 = ecx1

reg1 = ebx1

reg0 = 指向协程2的call_swap函数调用下一条指令的指针 = eax1

-----低地址-----

        xorl %eax, %eax

将eax寄存器清空,即返回值置为0

        ret

当前协程(协程1)的栈

-----高地址-----

指向协程2上下文的指针

指向协程1上下文的指针 <- eax

指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

下一个协程(协程2)的栈

-----高地址-----

指向协程1上下文的指针

指向协程2上下文的指针 <- esp

-----低地址-----

协程上下文1:

-----高地址-----

reg7 = eax

reg6 = ebp

reg5 = esi

reg4 = edi

reg3 = edx

reg2 = ecx

reg1 = ebx

reg0 = 指向协程1的call_swap函数调用下一条指令的指针

-----低地址-----

协程上下文2:

-----高地址-----

reg7 = esp1

reg6 = ebp1

reg5 = esi1

reg4 = edi1

reg3 = edx1

reg2 = ecx1

reg1 = ebx1

reg0 = 指向协程2的call_swap函数调用下一条指令的指针 = eax1

-----低地址-----

eip = 指向协程2的call_swap函数调用下一条指令的指针