zoukankan      html  css  js  c++  java
  • [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
    ");
               printf("func1: swapcontext(&uctx_func1, &uctx_func2)
    ");
               if (swapcontext(&uctx_func1, &uctx_func2) == -1)
                   handle_error("swapcontext");
               printf("func1: returning
    ");
           }
    
           static void
           func2(void)
           {
               printf("func2: started
    ");
               printf("func2: swapcontext(&uctx_func2, &uctx_func1)
    ");
               if (swapcontext(&uctx_func2, &uctx_func1) == -1)
                   handle_error("swapcontext");
               printf("func2: returning
    ");
           }
    
           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)
    ");
               if (swapcontext(&uctx_main, &uctx_func2) == -1)
                   handle_error("swapcontext");
    
               printf("main: exiting
    ");
               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)
    ");
    
        r = setjmp(bufferA);
        if (r == 0) routineB();
    
        printf("(A2) r=%d
    ",r);
    
        r = setjmp(bufferA);
        if (r == 0) longjmp(bufferB, 20001);
    
        printf("(A3) r=%d
    ",r);
    
        r = setjmp(bufferA);
        if (r == 0) longjmp(bufferB, 20002);
    
        printf("(A4) r=%d
    ",r);
    }
    
    void routineB()
    {
        int r;
    
        printf("(B1)
    ");
    
        r = setjmp(bufferB);
        if (r == 0) longjmp(bufferA, 10001);
    
        printf("(B2) r=%d
    ", r);
    
        r = setjmp(bufferB);
        if (r == 0) longjmp(bufferA, 10002);
    
        printf("(B3) r=%d
    ", 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) 
  • 相关阅读:
    Android推送服务——百度云推送
    关于HierarchyViewer的使用
    Android开发者的演示工具——asm.jar
    重装huson遇到的一些错误及解决
    对比语法错误、语义错误以及运行时错误
    对比常量存储与堆栈
    php下载文件夹目录下的文件
    B/S(WEB)系统中使用Activex插件调用扫描仪实现连续扫描并上传图像(IE文件扫描并自动上传
    上传文件夹
    如何在Web页面里使用高拍仪扫描上传图像
  • 原文地址:https://www.cnblogs.com/hugetong/p/14378526.html
Copyright © 2011-2022 走看看