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 : ®s[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 -> ®s[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函数调用下一条指令的指针