[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   toong  阅读(1058)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
历史上的今天:
2020-02-05 [nginx] nginx的hash与bucket size分析
2019-02-05 [math] 什么是双曲函数(转发)
2019-02-05 [math] 我对对数的最新理解

统计

点击右上角即可分享
微信分享提示