文章目录
- ucontext
- 相关函数
- 例子
- ucontext_t结构
- setjump、longjump
- 相关函数
- 例子
- jmp_buf结构
- 汇编实现
- 解析
- 图示
ucontext
相关函数
#include <ucontext.h>
int getcontext(ucontext_t *ucp);初始化ucp结构体,将当前上下文保存在ucp中。
int setcontext(const ucontext_t *ucp);设置当前上下文为ucp所指向的上下文。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);设置执行的函数指针,调用之前要先调用getcontext()
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);保存当前上下文到oucp,并且切换上下文到ucp
例子
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>// 栈大小
#define STACK_SIZE 1024ucontext_t *coroutineA, *coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程swapcontext(coroutineB, coroutineA);printf("D");// 切换运行协程swapcontext(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = (ucontext_t*)malloc(sizeof(ucontext_t));// 创建协程coroutineB = (ucontext_t*)malloc(sizeof(ucontext_t));char *stack = (char*)malloc(STACK_SIZE);getcontext(coroutineB);coroutineB->uc_stack.ss_sp = stack;coroutineB->uc_stack.ss_size = STACK_SIZE;coroutineB->uc_link = coroutineA;makecontext(coroutineB, coroutineStart1, 0);// 切换运行协程swapcontext(coroutineA, coroutineB);printf("C");// 切换运行协程swapcontext(coroutineA, coroutineB);printf("E\n");free(stack);free(coroutineB);free(coroutineA);return 0;
}
运行结果:
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ gcc -O0 -o ucontext ucontext.c
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./ucontext
ABCDE
ucontext_t结构
#ifdef __USE_MISC
# define __ctx(fld) fld
#else
# define __ctx(fld) __ ## fld
#endif/* Userlevel context. */
typedef struct ucontext_t{unsigned long int __ctx(uc_flags);struct ucontext_t *uc_link; // 当前context执行结束之后要执行的下一个contextstack_t uc_stack; // 上下文所需要的栈mcontext_t uc_mcontext; // 执行上下文sigset_t uc_sigmask; // 信号掩码struct _libc_fpstate __fpregs_mem; // fpu寄存器__extension__ unsigned long long int __ssp[4]; // 栈顶指针} ucontext_t;
栈:
/* Structure describing a signal stack. */
typedef struct{void *ss_sp; // 栈底指针int ss_flags; // SS_ONSTACK - 表明进程正在备选信号栈上执行 SS_DISABLE - 备用堆栈被停用size_t ss_size; // 栈大小} stack_t;
上下文:
/* Context to describe whole processor state. */
typedef struct{gregset_t __ctx(gregs); // 通用寄存器/* Note that fpregs is a pointer. */fpregset_t __ctx(fpregs); // fpu寄存器指针__extension__ unsigned long long __reserved1 [8];
} mcontext_t;/* Type for general register. */
__extension__ typedef long long int greg_t;/* Number of general registers. */
#define __NGREG 23
#ifdef __USE_MISC
# define NGREG __NGREG
#endif/* Container for all general registers. */
typedef greg_t gregset_t[__NGREG];/* Structure to describe FPU registers. */
typedef struct _libc_fpstate *fpregset_t;
setjump、longjump
相关函数
#include <setjmp.h>
int setjmp(jmp_buf env);将状态信息保存在env中。返回值:若直接调用则返回0,若从longjmp调用返回则返回非0值的longjmp中的val值。
void longjmp(jmp_buf env, int val);调用此函数则返回到语句setjmp所在的地方,其中env就是setjmp中的 env,而val则是使setjmp的返回值变为val。
例子
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>// 栈运行函数指针类型
typedef void(*coroutineStart)();// 切换栈
#define yieldTo(srcCoroutine, descCoroutine) \
if (0 == setjmp(srcCoroutine->buf)) \
{ \descCoroutine->jmp(); \
}class coroutine
{
public:jmp_buf buf;coroutineStart entry;bool called;coroutine(coroutineStart entry){this->entry = entry;called = (entry == NULL);}void jmp(){if (called){longjmp(buf, 1);}else{called = true;entry();}}
};coroutine* coroutineA, * coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程yieldTo(coroutineB, coroutineA);printf("D");// 切换运行协程yieldTo(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = new coroutine(NULL);// 创建协程coroutineB = new coroutine(coroutineStart1);// 切换运行协程yieldTo(coroutineA, coroutineB);printf("C");// 切换运行协程yieldTo(coroutineA, coroutineB);printf("E\n");delete coroutineA;delete coroutineB;return 0;
}
运行结果:
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ g++ -O0 -o setjump setjump.cpp
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./setjump
ABCDE
jmp_buf结构
typedef struct __jmp_buf_tag jmp_buf[1]; // 这边为什么要设置成只有一个长度的数组?/* Calling environment, plus possibly a saved signal mask. */
struct __jmp_buf_tag{/* NOTE: The machine-dependent definitions of `__sigsetjmp'assume that a `jmp_buf' begins with a `__jmp_buf' and that`__mask_was_saved' follows it. Do not move these membersor add others before it. */__jmp_buf __jmpbuf; /* Calling environment. */ // 环境信息int __mask_was_saved; /* Saved the signal mask? */ // 信号是否保存__sigset_t __saved_mask; /* Saved signal mask. */ // 信号集};
为什么要设置成只有一个长度的数组?
sizeof(数组名) 为数组长度
&数组名 得到的是数组指针,数组指针的步长为整个数组的长度
作为函数参数退化为指针
buffer:
# if __WORDSIZE == 64
typedef long int __jmp_buf[8]; // 8个寄存器的保存
# elif defined __x86_64__
__extension__ typedef long long int __jmp_buf[8];
# else
typedef int __jmp_buf[6];
# endif
setjmp 在 glibc 的实现:
ENTRY (__sigsetjmp)/* Save registers. */movq %rbx, (JB_RBX*8)(%rdi)
#ifdef PTR_MANGLE // 这里对%rbp、%rsp、%rip加密了
# ifdef __ILP32__/* Save the high bits of %rbp first, since PTR_MANGLE willonly handle the low bits but we cannot presume %rbp isbeing used as a pointer and truncate it. Here we write allof %rbp, but the low bits will be overwritten below. */movq %rbp, (JB_RBP*8)(%rdi)
# endifmov %RBP_LP, %RAX_LPPTR_MANGLE (%RAX_LP)mov %RAX_LP, (JB_RBP*8)(%rdi)
#elsemovq %rbp, (JB_RBP*8)(%rdi)
#endifmovq %r12, (JB_R12*8)(%rdi)movq %r13, (JB_R13*8)(%rdi)movq %r14, (JB_R14*8)(%rdi)movq %r15, (JB_R15*8)(%rdi)lea 8(%rsp), %RDX_LP /* Save SP as it will be after we return. */
#ifdef PTR_MANGLEPTR_MANGLE (%RDX_LP)
#endifmovq %rdx, (JB_RSP*8)(%rdi)mov (%rsp), %RAX_LP /* Save PC we are returning to now. */LIBC_PROBE (setjmp, 3, LP_SIZE@%RDI_LP, -4@%esi, LP_SIZE@%RAX_LP)
#ifdef PTR_MANGLEPTR_MANGLE (%RAX_LP)
#endifmovq %rax, (JB_PC*8)(%rdi)
信号集:
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
汇编实现
看实现之前可以先看一下另一个博客特殊的栈溢出例子
#include <stdio.h>
#include <stdlib.h>// 栈大小
#define STACK_SIZE 1024// 栈运行函数指针类型
typedef void(*coroutineStart)();class coroutine
{
public:long* stack_pointer;char* stack;coroutine(coroutineStart entry){if (entry == NULL){stack = NULL;stack_pointer = NULL;return;}// 栈申请(堆空间模拟)stack = (char*)malloc(STACK_SIZE);// 栈基址char* base = stack + STACK_SIZE;stack_pointer = (long*) base;// 写入函数地址stack_pointer -= 1;*stack_pointer = (long) entry;// 写入栈基址stack_pointer -= 1;*stack_pointer = (long) base;}~coroutine(){if (!stack){return;}free(stack);stack = NULL;}
};// 切换栈
void yieldTo(coroutine* srcCoroutine, coroutine* descCoroutine)
{__asm__ ("movq %%rsp, %0\n\t""movq %%rax, %%rsp\n\t":"=m"(srcCoroutine->stack_pointer):"a"(descCoroutine->stack_pointer):);
}coroutine *coroutineA, *coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程yieldTo(coroutineB, coroutineA);printf("D");// 切换运行协程yieldTo(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = new coroutine(NULL);// 创建协程coroutineB = new coroutine(coroutineStart1);// 切换运行协程yieldTo(coroutineA, coroutineB);printf("C");// 切换运行协程yieldTo(coroutineA, coroutineB);printf("E\n");delete coroutineA;delete coroutineB;return 0;
}
运行结果:
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ g++ -g -o coroutine -O0 80x86.cpp
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./coroutine
ABCDE
解析
用堆模拟栈的时候,堆栈的生长方向是相反的。
其中__asm__
的代码:
:"a"(descCoroutine->stack_pointer)
表示把descCoroutine->stack_pointer
变量的值给到rax
寄存器当做输入:"=m"(srcCoroutine->stack_pointer)
表示用srcCoroutine->stack_pointer
承接值"movq %%rsp, %0\n\t"
表示把rsp
的值给到%0
也就是srcCoroutine->stack_pointer
"movq %%rax, %%rsp\n\t"
表示把rax
的值给到rsp
,其中rax
的值就是descCoroutine->stack_pointer
也就是说yieldTo
函数主要做的工作就是:
- 把当前的
rsp
给到srcCoroutine->stack_pointer
- 把
descCoroutine->stack_pointer
给到rsp
- 函数返回的时候会把
rbp
切换成descCoroutine
的栈基址
使用objdump看一下:
g++ -g -o coroutine -O0 80x86.cpp
objdump -d coroutine00000000004007f8 <main>:
...40084d: 48 8b 15 1c 08 20 00 mov 0x20081c(%rip),%rdx // descCoroutine400854: 48 8b 05 0d 08 20 00 mov 0x20080d(%rip),%rax // srcCoroutine40085b: 48 89 d6 mov %rdx,%rsi // descCoroutine40085e: 48 89 c7 mov %rax,%rdi // srcCoroutine400861: e8 27 ff ff ff callq 40078d <_Z7yieldToP9coroutineS0_>000000000040078d <_Z7yieldToP9coroutineS0_>:40078d: 55 push %rbp40078e: 48 89 e5 mov %rsp,%rbp400791: 48 89 7d f8 mov %rdi,-0x8(%rbp) // 栈中开辟临时变量srcCoroutine400795: 48 89 75 f0 mov %rsi,-0x10(%rbp) // 栈中开辟临时变量descCoroutine400799: 48 8b 45 f0 mov -0x10(%rbp),%rax // descCoroutine -> rax40079d: 48 8b 00 mov (%rax),%rax // 指针偏移索引成员变量stack_pointer,这边真好是+04007a0: 48 8b 55 f8 mov -0x8(%rbp),%rdx // srcCoroutine -> rdx4007a4: 48 89 22 mov %rsp,(%rdx) // 老rsp -> srcCoroutine->stack_pointer4007a7: 48 89 c4 mov %rax,%rsp // descCoroutine->stack_pointer -> rsp4007aa: 5d pop %rbp4007ab: c3 retq
图示
创建AB协程:
第一次yieldTo
,切到coroutineStart1
:
第二次yieldTo
,切到main
: