setjmp与longjmp实现协程切换....

    最近关注协程切换!所以特别到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)。

 

    上述代码尽量打上了足够多的注释,方便大家参考、学习、指点。

    如果有任何疑问、建议,也可以留言分享、探讨。^_^

转载于:https://my.oschina.net/CandyMi/blog/917551

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
setjmplongjmp是C语言中提供的两个非常重要的函数,目的是实现函数间的跳转和状态保存。 setjmp函数用于保存当前函数执行的上下文,同时返回一个int类型的值。这个值由setjmp函数自动生成,用于之后调用longjmp函数时确定跳转目标。 longjmp函数则用于恢复之前setjmp所保存下来的上下文,使得程序能够直接跳转到指定的目标。 下面是setjmplongjmp在mips汇编语言中的实现setjmp: ``` #setjmp implementation in MIPS assembly language .text .align 2 .globl setjmp .ent setjmp setjmp: addi sp, sp, -32 # allocate stack frame sw ra, 28(sp) # save return address sw fp, 24(sp) # save frame pointer addi fp, sp, 32 # set new frame pointer sw a0, 0(fp) # save jmp_buf li v0, 0 # return 0 jr ra .end setjmp ``` longjmp: ``` #longjmp implementation in MIPS assembly language .text .align 2 .globl longjmp .ent longjmp longjmp: lw fp, 24(sp) # restore frame pointer lw ra, 28(sp) # restore return address addi sp, sp, 32 # deallocate stack frame lw t0, 0(fp) # restore jmp_buf lw ra, 4(t0) # restore return address lw sp, 8(t0) # restore stack pointer lw s0, 12(t0) # restore frame pointer jr ra .end longjmp ``` 以上是setjmplongjmp在MIPS汇编语言中的实现。需要注意的是,这里使用了MIPS体系结构的调用约定,即函数返回值在v0寄存器中,函数参数在a0-a3寄存器中,被调用者保存的寄存器在栈帧中。在实际使用中,应根据特定的编译器和操作系统的要求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值