[openssl] openssl与协程

[classic_tong:  https://www.cnblogs.com/hugetong/p/14378526.html]

Golang就用了协程,一直没有想过是怎么实现的。
今天读openssl的源码,读到这个地方:https://github.com/openssl/openssl/blob/master/crypto/async/async.c#L146
原来,这就是协程。

C语言里,实现协程,有两个方法:

swapcontext

  • getcontext:获取当前context
  • setcontext:切换到指定context
  • makecontext: 用于将一个新函数和堆栈,绑定到指定context中
  • swapcontext:保存当前context,并且切换到指定context

man swapcontext 可以得到下面这个例子

       #include <ucontext.h>
       #include <stdio.h>
       #include <stdlib.h>

       static ucontext_t uctx_main, uctx_func1, uctx_func2;

       #define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

       static void
       func1(void)
       {
           printf("func1: started\n");
           printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
           if (swapcontext(&uctx_func1, &uctx_func2) == -1)
               handle_error("swapcontext");
           printf("func1: returning\n");
       }

       static void
       func2(void)
       {
           printf("func2: started\n");
           printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
           if (swapcontext(&uctx_func2, &uctx_func1) == -1)
               handle_error("swapcontext");
           printf("func2: returning\n");
       }

       int
       main(int argc, char *argv[])
       {
           char func1_stack[16384];
           char func2_stack[16384];

           if (getcontext(&uctx_func1) == -1)
               handle_error("getcontext");
           uctx_func1.uc_stack.ss_sp = func1_stack;
           uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
           uctx_func1.uc_link = &uctx_main;
           makecontext(&uctx_func1, func1, 0);

           if (getcontext(&uctx_func2) == -1)
               handle_error("getcontext");
           uctx_func2.uc_stack.ss_sp = func2_stack;
           uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
           /* Successor context is f1(), unless argc > 1 */
           uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;
           makecontext(&uctx_func2, func2, 0);

           printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
           if (swapcontext(&uctx_main, &uctx_func2) == -1)
               handle_error("swapcontext");

           printf("main: exiting\n");
           exit(EXIT_SUCCESS);
       }

 

setjmp/longjmp

存下当前的函数栈,然后跳转。

Setjmp() and longjmp() are subroutines that let you perform complex flow-of-control in C/Unix.
One of the keys to understanding setjmp() and longjmp() is to understand machine layout, as described 
in the assembler and malloc lectures of the past few weeks. The state of a program depends completely
on the contents of its memory (i.e. the code, globals, heap, and stack), and the contents of its registers.
The contents of the registers includes the stack pointer (sp), frame pointer (fp), and program counter (pc).
What setjmp() does is save the contents of the registers so that longjmp() can restore them later.
In this way, longjmp() ``returns'' to the state of the program when setjmp() was called.

 

例子
#include <stdio.h>
#include <setjmp.h>

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration 

void routineA()
{
    int r ;

    printf("(A1)\n");

    r = setjmp(bufferA);
    if (r == 0) routineB();

    printf("(A2) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20001);

    printf("(A3) r=%d\n",r);

    r = setjmp(bufferA);
    if (r == 0) longjmp(bufferB, 20002);

    printf("(A4) r=%d\n",r);
}

void routineB()
{
    int r;

    printf("(B1)\n");

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10001);

    printf("(B2) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10002);

    printf("(B3) r=%d\n", r);

    r = setjmp(bufferB);
    if (r == 0) longjmp(bufferA, 10003);
}


int main(int argc, char **argv) 
{
    routineA();
    return 0;
}

http://web.eecs.utk.edu/~huangj/cs360/360/notes/Setjmp/lecture.html

https://stackoverflow.com/questions/14685406/practical-usage-of-setjmp-and-longjmp-in-c

 

区别

两套api的区别在于,swapcontext可以理解为longjmp的替代者。longjmp在每次切换的时候会将当前调用栈的内容拷贝出来,然后沿用原来的地址进入新的调用栈,

下次切换的时候会将当前的调用栈内容也拷贝出来,然后用之前的内容覆盖当前的栈地址指向的内容。swapcontext优化了这个过程,不再进行拷贝,而是在切换

的时候将寄存器指针指向预分配好的堆地址空间上,再次切换的时候再将地址指回去原来的调用栈地址去。这样便在完成thread切换的同时省去了拷贝。

详见一个分析例子,涉及到了这部分知识:[openssl] openssl asynch_mode 使用libasan时的OOM问题

 

怎么GDB

1. 断在makeconext(func) 传入的这个func上.
2. 断在setjmp的下一行.

例如调试async模式的openssl的时候

1. 断在函数async_start_func上.

2. 断在了下面函数的第 58 行

(gdb) l async_fibre_swapcontext
50      static ossl_inline int async_fibre_swapcontext(async_fibre *o, async_fibre *n, int r)
51      {
52      #  ifdef USE_SWAPCONTEXT
53          swapcontext(&o->fibre, &n->fibre);
54      #  else
55          o->env_init = 1;
56
57          if (!r || !_setjmp(o->env)) {
58              if (n->env_init)
59                  _longjmp(n->env, 1);
(gdb) 

 

posted on 2021-02-05 16:33  toong  阅读(1013)  评论(0编辑  收藏  举报