最近关注协程切换!所以特别到libdill"借"了点代码,然后自己实现了一个简单的。
由于简单实现,基本使用的都是宏。能不依赖尽量不依赖!
编译命令:cc -o main main.c -Wall -O3
运行命令: ./main
Main.h
/* 64位编译器的栈保存操作*/
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
#include <setjmp.h>
#define setjmp(ctx) ({\
int ret;\
asm("lea LJMPRET%=(%%rip), %%rcx\n\t"\
"xor %%rax, %%rax\n\t"\
"mov %%rbx, (%%rdx)\n\t"\
"mov %%rbp, 8(%%rdx)\n\t"\
"mov %%r12, 16(%%rdx)\n\t"\
"mov %%r13, 24(%%rdx)\n\t"\
"mov %%r14, 32(%%rdx)\n\t"\
"mov %%r15, 40(%%rdx)\n\t"\
"mov %%rsp, 48(%%rdx)\n\t"\
"mov %%rcx, 56(%%rdx)\n\t"\
"LJMPRET%=:\n\t"\
: "=a" (ret)\
: "d" (ctx)\
: "memory", "rcx", "rsi", "rdi", "r8", "r9", "r10", "r11", "cc");\
ret;\
})
#define longjmp(ctx) \
asm("movq 56(%%rdx), %%rcx\n\t"\
"movq 48(%%rdx), %%rsp\n\t"\
"movq 40(%%rdx), %%r15\n\t"\
"movq 32(%%rdx), %%r14\n\t"\
"movq 24(%%rdx), %%r13\n\t"\
"movq 16(%%rdx), %%r12\n\t"\
"movq 8(%%rdx), %%rbp\n\t"\
"movq (%%rdx), %%rbx\n\t"\
".cfi_def_cfa %%rdx, 0 \n\t"\
".cfi_offset %%rbx, 0 \n\t"\
".cfi_offset %%rbp, 8 \n\t"\
".cfi_offset %%r12, 16 \n\t"\
".cfi_offset %%r13, 24 \n\t"\
".cfi_offset %%r14, 32 \n\t"\
".cfi_offset %%r15, 40 \n\t"\
".cfi_offset %%rsp, 48 \n\t"\
".cfi_offset %%rip, 56 \n\t"\
"jmp *%%rcx\n\t"\
: : "d" (ctx), "a" (1))
/* 32位编译器的栈保存操作*/
#elif defined(__i386__) && (defined(__GNUC__) || defined(__clang__))
#include <setjmp.h>
#define setjmp(ctx) ({\
int ret;\
asm("movl $LJMPRET%=, %%ecx\n\t"\
"movl %%ebx, (%%edx)\n\t"\
"movl %%esi, 4(%%edx)\n\t"\
"movl %%edi, 8(%%edx)\n\t"\
"movl %%ebp, 12(%%edx)\n\t"\
"movl %%esp, 16(%%edx)\n\t"\
"movl %%ecx, 20(%%edx)\n\t"\
"xorl %%eax, %%eax\n\t"\
"LJMPRET%=:\n\t"\
: "=a" (ret) : "d" (ctx) : "memory");\
ret;\
})
#define longjmp(ctx) \
asm("movl (%%edx), %%ebx\n\t"\
"movl 4(%%edx), %%esi\n\t"\
"movl 8(%%edx), %%edi\n\t"\
"movl 12(%%edx), %%ebp\n\t"\
"movl 16(%%edx), %%esp\n\t"\
"movl 20(%%edx), %%ecx\n\t"\
".cfi_def_cfa %%edx, 0 \n\t"\
".cfi_offset %%ebx, 0 \n\t"\
".cfi_offset %%esi, 4 \n\t"\
".cfi_offset %%edi, 8 \n\t"\
".cfi_offset %%ebp, 12 \n\t"\
".cfi_offset %%esp, 16 \n\t"\
".cfi_offset %%eip, 20 \n\t"\
"jmp *%%ecx\n\t"\
: : "d" (ctx), "a" (1))
#else
//由于暂时无法优化与保障系统setjmp的稳定,所以暂时不对其进行支持;
#error "此平台或编译器不被支持......."
#endif
/*
//是否使用jemalloc
#ifdef __JEMALLOC__
#include <stdlib.h>
#include <jemalloc/jemalloc.h>
#else
#include <stdlib.h>
#endif
*/
// 这个是主协程的宏;
typedef sigjmp_buf Hub;
// 这个子协程专用宏;
typedef sigjmp_buf context;
static Hub Loop,init;
int main_status = 0;
//每次都需要检查这玩意是否为空、是否可用
context next;
//每个协程函数需要前置指定此标志
#define coroutine __attribute__((noinline)) void
int empty = 1;
#define __main__ \
main_status = 1;\
switch(setjmp(Loop)){\
case 0:\
longjmp(init);\
case 1:\
/*这里需要判断是否有需要运行的协程..*/\
if(empty)\
break;\
longjmp(next);\
}
//yield API依赖于begin....end之间。
#define yield() do{\
/*这里要判断是否为初次yield,否则后续会yield会出现死循环.*/\
if(!yielded){\
yielded = 1;\
/*这里将当前上下文保存在main函数要轮询的next指针中,开关为empty。*/\
if(!setjmp(next)){\
empty=0;\
longjmp(Loop);}\
/*什么时候会到这来?第一次yield设置上下文并且打开empty开关后则会到这来*/\
break;\
}\
/*发现不是初次yield,重新设置当前上线文!*/\
if(!setjmp(next)){\
longjmp(Loop);}\
/*每次回来后将开关关上,防止有一次跑到死循环上*/\
else\
{empty=1;break;}\
}while(0)
#define go(fn) {\
/*通过提供的go api来初始化主协程,顺便检查是否已经主协程是否已经初始化*/\
if(!main_status){\
/*这个点是初始化主协程和初始化完毕后跳出来执行任务..*/\
if(!setjmp(init))\
{__main__;}\
else\
{fn;}\
}else{\
/*如果初始化了主协程,则直接运行任务函数!通常会在此调用提供的"阻塞"API*/\
fn;\
/*这个是函数运行完毕后就会在main函数运行下一条语句*/\
}\
}
#define begin {\
/*这里检测是否初次yield*/\
int yielded = 0;\
#define end \
}
Main.c
#include <stdio.h>
#include "main.h"
coroutine work()
begin
printf("Hello work!\n");
yield();
printf("bye work!\n");
end
coroutine gwork()
begin
printf("Hello gwork!\n");
yield();
printf("bye gwork!\n");
end
int main(int argc, char const *argv[])
{
go(work());
go(gwork());
printf("End.......\n");
return 0;
}
输出结果:
[root@localhost ~]# ./main
Hello work!
bye work!
Hello gwork!
bye gwork!
End.......
[root@localhost ~]#
说实话,这个实现是个半成品!
毫无疑问,系统提供的setjmp根本就不能拿来用(实现协程)...
至于为什么不继续研究下去?原因真的是精力有限,且代码量也比较大!(可以参考libdill代码量)
优化的地方就别说了,加紧加急赶出来的。(午休什么的都一直在思考代码.......)
但是能在几天内实现,说实话都是耗费了很大精力来弄......(语法限制也是很大一部分)
我的git上也有剥离下来的libdill代码,打包成静态文件后尽有60K大小!
有兴趣的同学可以下载下来,按照指示编译。(libdill仅支持Linux、Unix、BSD等OS)。
上述代码尽量打上了足够多的注释,方便大家参考、学习、指点。
如果有任何疑问、建议,也可以留言分享、探讨。^_^