OpenSSL异步模式流程梳理

打印 上一主题 下一主题

主题 913|帖子 913|积分 2739

源码

来源于 OpenSSL Master Commit ID d550d2aae531c6fa2e10b1a30d2acdf373663889。
总览

焦点入口函数为 ssl_start_async_job,以 SSL_do_handshake 为入口举例分析,同时通过标注步骤【1~N】,来明白阅读的次序。

  • 步骤【1】到步骤【18】为一个阶段
  • 步骤【19】到步骤【23】为一个阶段
  • 步骤【24】到步骤【34】之后为一个阶段
    为了减少阅读的干扰和提升阅读服从,不相干的代码省略,同时部分代码会在表明中展示
流程

SSL_do_handshake
  1. int SSL_do_handshake(SSL *s)
  2. {
  3.     SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
  4.     /* 步骤【24】
  5.      * 再次进入
  6.      */
  7.     if ((sc->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
  8.         /* 步骤【1】
  9.          * 检测是否开启了异步模式,并且没有正在运行的 job
  10.          * 通过 ASYNC_get_current_job 可获取当前正在运行的 job
  11.          * ASYNC_get_current_job 中通过 async_get_ctx 获取线程变量 ctx
  12.          * ctx 中的字段 currjob 即为返回值
  13.          */
  14.         struct ssl_async_args args;
  15.         memset(&args, 0, sizeof(args));
  16.         args.s = s;
  17.         /* 步骤【2】
  18.          * 通过 ssl_start_async_job 启动一个异步 job
  19.          * 关注一下 args 中赋值的唯一一个参数 s
  20.          *
  21.          * 步骤【25】
  22.          * 通过 ssl_start_async_job 启动一个异步 job
  23.          */
  24.         ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
  25.         /* 步骤【18】
  26.          * 至此通过 ssl_start_async_job 得到了与 else 中一样的返回值
  27.          * 因为提到过 ssl_start_async_job 中最终也是执行的 sc->handshake_func,且执行完成
  28.          *
  29.          * 步骤【23】
  30.          * 返回值为 -1,但此时 job,并没有完成
  31.          * 函数的调用者需要有能力处理这种情况,并且在适当实际继续执行 job
  32.          * 这就需要调用者去适配 OpenSSL 异步模式
  33.          */
  34.     } else {
  35.         ret = sc->handshake_func(s);
  36.     }
  37. }
复制代码
ssl_start_async_job
  1. static int ssl_start_async_job(SSL *s, struct ssl_async_args *args,
  2.                                int (*func) (void *))
  3. {
  4.     /* 步骤【3】
  5.      * 启动一个 job
  6.      * sc->job 在首次调用时为 NULL
  7.      * args.s 为 s
  8.      * func 为 ssl_do_handshake_intern
  9.      *
  10.      * 步骤【26】
  11.      */
  12.     switch (ASYNC_start_job(&sc->job, sc->waitctx, &ret, func, args,
  13.                             sizeof(struct ssl_async_args))) {
  14.     /* 步骤【16】
  15.      * 返回值为 ASYNC_FINISH
  16.      *
  17.      * 步骤【21】
  18.      * 返回值为 ASYNC_PAUSE
  19.      */
  20.     case ASYNC_ERR:
  21.         sc->rwstate = SSL_NOTHING;
  22.         ERR_raise(ERR_LIB_SSL, SSL_R_FAILED_TO_INIT_ASYNC);
  23.         return -1;
  24.     case ASYNC_PAUSE:
  25.         /* 步骤【22】
  26.          */
  27.         sc->rwstate = SSL_ASYNC_PAUSED;
  28.         return -1;
  29.     case ASYNC_NO_JOBS:
  30.         sc->rwstate = SSL_ASYNC_NO_JOBS;
  31.         return -1;
  32.     case ASYNC_FINISH:
  33.         /* 步骤【17】
  34.          */
  35.         sc->job = NULL;
  36.         return ret;
  37.     default:
  38.         sc->rwstate = SSL_NOTHING;
  39.         ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
  40.         /* Shouldn't happen */
  41.         return -1;
  42.     }
  43. }
复制代码
ASYNC_start_job
  1. int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *wctx, int *ret,
  2.                     int (*func)(void *), void *args, size_t size)
  3. {
  4.     /* 步骤【4】
  5.      * ctx 为线程变量,对线程而言全局可见,当前为第一次
  6.      * 会创建 ctx,后续的 job 都能通过 async_get_ctx() 获取
  7.      *
  8.      * 步骤【27】
  9.      * ctx 为线程变量,之前已经创建过,可以直接获取
  10.      */
  11.     ctx = async_get_ctx();
  12.     if (ctx == NULL)
  13.         ctx = async_ctx_new();
  14.     if (ctx == NULL)
  15.         return ASYNC_ERR;
  16.     /* 步骤【5】
  17.      * 首次调用 *job 应为 NULL
  18.      *
  19.      * 步骤【28】
  20.      * job 有值
  21.      */
  22.     if (*job != NULL)
  23.         ctx->currjob = *job;
  24.     for (;;) {
  25.         /* 步骤【6】
  26.          * ctx 刚创建,ctx->currjob 为 NULL,跳过
  27.          *
  28.          * 步骤【14】
  29.          * 此时 ctx->currjob 有值,且 ctx->currjob->status 为 ASYNC_JOB_STOPPING
  30.          *
  31.          * 步骤【29】
  32.          * 此时 ctx->currjob 有值,且 ctx->currjob->status 为 ASYNC_JOB_PAUSED
  33.          */
  34.         if (ctx->currjob != NULL) {
  35.             if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
  36.                 /* 步骤【15】
  37.                  * job 完成,返回 ASYNC_FINISH
  38.                  * 此时,可以释放针对该 job 申请的各种资源
  39.                  * 重置线程变量 ctx 上有关的值
  40.                  */
  41.                 *ret = ctx->currjob->ret;
  42.                 ctx->currjob->waitctx = NULL;
  43.                 async_release_job(ctx->currjob);
  44.                 ctx->currjob = NULL;
  45.                 *job = NULL;
  46.                 return ASYNC_FINISH;
  47.             }
  48.             if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
  49.                 /* 步骤【20】
  50.                  * job 暂停,返回 ASYNC_PAUSE
  51.                  * 此时,不要释放针对该 job 申请的各种资源
  52.                  * 仅仅将 currjob 设置为 NULL
  53.                  */
  54.                 *job = ctx->currjob;
  55.                 ctx->currjob->status = ASYNC_JOB_PAUSED;
  56.                 ctx->currjob = NULL;
  57.                 return ASYNC_PAUSE;
  58.             }
  59.             if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
  60.                 /* 步骤【30】
  61.                  * 将 ctx->currjob->fibrectx->fibre 作为目标上下文切换
  62.                  * ctx->currjob->fibrectx->fibre 中保存的即为
  63.                  * ASYNC_pause_job 中切换上下文时保存的信息
  64.                  * 当前上下午的信息保存在 ctx->dispatcher->fibre 中
  65.                  */
  66.                 if (!async_fibre_swapcontext(&ctx->dispatcher,
  67.                         &ctx->currjob->fibrectx, 1)) {
  68.                     ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
  69.                     ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
  70.                     goto err;
  71.                 }
  72.                 /* 步骤【34】
  73.                  * 上下文切换回此,由于 job 已经完成,status 为 ASYNC_JOB_STOPPING
  74.                  * 接下来的流程与步骤【15】相同
  75.                  */
  76.                 continue;
  77.             }
  78.         }
  79.         /* 步骤【7】
  80.          * 由于 job 尚未创建,通过 async_get_pool_job 从池子中获取一个 job
  81.          * 由于 job 中有参数需要关注,所以还要看一下 async_get_pool_job 的实现
  82.          * 具体的 async_get_pool_job 中调用 async_job_new 创建 job
  83.          * 然后调用 async_fibre_makecontext 初始化 job->fibrectx
  84.          * 使用 makecontext(&fibre->fibre, async_start_func, 0),其中的 fibre 即 job->fibrectx
  85.          * makecontext 是 libc 提供的库,与之对应的是 swapcontext
  86.          * makecontext 用于组装上下文信息,swapcontext 用于切换上下文信息
  87.          * 后续有关 async_fibre_makecontext 和 async_fibre_swapcontext 其实就是
  88.          * makecontext 与 swapcontext
  89.          * 当通过 swapcontext 切换上下文,切换的目标为 job->fibrectx->fibre,就会调用 async_start_func
  90.          */
  91.         if ((ctx->currjob = async_get_pool_job()) == NULL)
  92.             return ASYNC_NO_JOBS;
  93.         /* 步骤【8】
  94.          * 将 args,func(ssl_do_handshake_intern)等参数绑定至 ctx->currjob
  95.          */
  96.         if (args != NULL) {
  97.             ctx->currjob->funcargs = OPENSSL_malloc(size);
  98.             if (ctx->currjob->funcargs == NULL) {
  99.                 async_release_job(ctx->currjob);
  100.                 ctx->currjob = NULL;
  101.                 return ASYNC_ERR;
  102.             }
  103.             memcpy(ctx->currjob->funcargs, args, size);
  104.         } else {
  105.             ctx->currjob->funcargs = NULL;
  106.         }
  107.         ctx->currjob->func = func;
  108.         ctx->currjob->waitctx = wctx;
  109.         /* 步骤【9】
  110.          * 至此,job 初始化完成
  111.          * 其中有几个关键参数,再总结一下,即
  112.          * func 为 ssl_do_handshake_intern
  113.          * args 中的 args.s 为 ssl_start_async_job 传入的 s
  114.          * 该 job 也同时被绑定到了线程变量 ctx 的 currjob 上
  115.          * job->fibrectx->fibre 与 async_start_func 绑定
  116.          */
  117.         /* 步骤【10】
  118.          * 这是关键,用于做上下文切换,即上文提到过的
  119.          * swapcontext(&o->fibre, &n->fibre)
  120.          * 第一个参数中的 o 与第二个参数中的 n,分别对应 &ctx->dispatcher 和 &ctx->currjob->fibrectx
  121.          * 第二个参数是目标,而这个目标就是上文反复提到的 job->fibrectx->fibre,因此会调用 async_start_func
  122.          * 第一个参数用来存储当前状态,即会记录此时的上下文信息
  123.          * 当下一次将 ctx->dispatcher 作为 swapcontext 的第二个参数时,就能恢复到此处继续执行
  124.          */
  125.         if (!async_fibre_swapcontext(&ctx->dispatcher,
  126.                 &ctx->currjob->fibrectx, 1)) {
  127.             ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
  128.             goto err;
  129.         }
  130.         /* 步骤【13】
  131.          * 在 async_start_func 中完成 job->func 后执行 swapcontext 切换回此
  132.          * 此时的 job->status 值为 ASYNC_JOB_STOPPING
  133.          *
  134.          * 步骤【19】
  135.          * 在 ASYNC_pause_job 中,执行 swapcontext 切换回此
  136.          * 此时的 job->status 值为 ASYNC_JOB_PAUSING
  137.          */
  138.     }
  139. }
复制代码
async_start_func
  1. void async_start_func(void)
  2. {
  3.     async_ctx *ctx = async_get_ctx();
  4.     while (1) {
  5.         /* 步骤【11】
  6.          * 此处所有参数在前文都重点提到过
  7.          * func 为 ssl_do_handshake_intern,参数 args,查看 ssl_do_handshake_intern
  8.          * 实际上,就是调用的 sc->handshake_func
  9.          * 这等同于步骤【1】中未开启异步模式的情况,区别就是现在是启动了一个 job 执行
  10.          */
  11.         job = ctx->currjob;
  12.         job->ret = job->func(job->funcargs);
  13.         /* 步骤【12】
  14.          * job->func 执行完成,即 job 结束
  15.          * 进行上下文切换,将 ctx->dispatcher 作为 swapcontext 的第二个参数
  16.          * 回头看看,就知道切换到哪里了,当前现场保存在 job->fibrectx->fibre 中
  17.          *
  18.          * 步骤【33】
  19.          * 暂停的 job 执行完成,上下文切换到目标 job->fibrectx->fibre
  20.          */
  21.         job->status = ASYNC_JOB_STOPPING;
  22.         if (!async_fibre_swapcontext(&job->fibrectx,
  23.                                      &ctx->dispatcher, 1)) {
  24.             ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
  25.         }
  26.     }
  27. }
复制代码
停息

读到此处,不知是否会有这样的疑问?OpenSSL 异步模式,异步体现在哪里?上述步骤中,不就是通过一个新的上下文去实行了 sc->handshake_func 吗?这与步骤【1】的 else 中,直接实行 sc->handshake_func 有什么区别吗?如果你产生了这样的疑问,那就证明上面的步骤你应该是理解了。由于如果按照步骤这样一路看下来,确实不是异步模式,而是同步模式。
这是由于另有一个重要的机制没有介绍,那就是停息,这个机制允许上下文实行到某个地方,切换出去,做别的事情,然后再切换返来必须实行。而这个机制起作用的地方出现在步骤【11】到步骤【12】之间,实行 job->func 中。
思量一种情况,job->func 中,必要进行签名验签,加密解密,而这些算法是卸载到硬件的,调用算法必要等待硬件返回,而在这一段时间中,只能壅闭,导致 CPU 利用率低,例如如下代码。
  1. void do_sign()
  2. {
  3.     // 获得一个硬件签名请求结构体并初始化
  4.     struct req *r = sign_req_alloc();
  5.     sign_req_init(r);
  6.     // 利用硬件 API 发起签名请求
  7.     // sign 中仅仅传递请求,就返回了,剩下就交给硬件
  8.     sign(r);
  9.     // 当硬件完成任务,会将 r->done 变为 true
  10.     // 此处阻塞,不断的轮询 r->done 是否变为 true 来检测任务是否完成
  11.     while (1) {
  12.         if (r->done) {
  13.             break;
  14.         }
  15.         usleep(0);
  16.     }
  17.     // 完成签名,退出
  18.     return;
  19. }
复制代码
可以看出,如果硬件没有完成使命,就会一直实行 while(1),无法退出,导致无法处置惩罚其他使命。如果硬件性能很高,壅闭时间很短,可能影响会比力小,但是,如果硬件性能较差,壅闭时间长,就会导致 CPU 利用率很低。此时就可以使用 OpenSSL 异步模式中提供的 ASYNC_pause_job,如下代码。
  1. void do_sign()
  2. {
  3.     // 获得一个硬件签名请求结构体并初始化
  4.     struct req *r = sign_req_alloc();
  5.     sign_req_init(r);
  6.     // 利用硬件 API 发起签名请求
  7.     // sign 中仅仅传递请求,就返回了,剩下就交给硬件
  8.     sign(r);
  9.     ASYNC_pause_job();
  10.     /* 步骤【32】
  11.      * do_sign 完成退出,会一直退到 async_start_func
  12.      */
  13.     // 完成签名,退出
  14.     return;
  15. }
复制代码
仅仅将必要壅闭等待的代码,更换为 ASYNC_pause_job 即可。
ASYNC_pause_job
  1. int ASYNC_pause_job(void)
  2. {
  3.     job = ctx->currjob;
  4.     job->status = ASYNC_JOB_PAUSING;
  5.     /* 上下文切换,会切换到步骤【13】的地方继续执行
  6.      * 需要注意的是,job 的状态设置成了 ASYNC_JOB_PAUSING
  7.      * 并且将当前的上下文保存在 job->fibrectx->fibre 中
  8.      * 紧接着步骤【19】
  9.      */
  10.     if (!async_fibre_swapcontext(&job->fibrectx,
  11.                                  &ctx->dispatcher, 1)) {
  12.         ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
  13.         return 0;
  14.     }
  15.     /* 步骤【31】
  16.      * 上下文切换之后,继续执行,就回到了 do_sign 中
  17.      */
  18. }
复制代码
恢复

前文提到过,调用者必要有本领处置惩罚 SSL_do_handshake 返回值 -1,但 job 还存在的情况,这意味着 SSL_do_handshake 中通过 ASYNC_pause_job 停息的 job。
而调用者必要在一个合适的机遇,去继续运行该 job,怎样确定什么时候是合适的机遇,这是调用者必要思量的题目,此处仅介绍怎样恢复 job 的实行,很简单,再次调用 SSL_do_handshake 即可。
回到流程中,此时从步骤【24】开始。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表