源码
来源于 OpenSSL Master Commit ID d550d2aae531c6fa2e10b1a30d2acdf373663889。
总览
焦点入口函数为 ssl_start_async_job,以 SSL_do_handshake 为入口举例分析,同时通过标注步骤【1~N】,来明白阅读的次序。
- 步骤【1】到步骤【18】为一个阶段
- 步骤【19】到步骤【23】为一个阶段
- 步骤【24】到步骤【34】之后为一个阶段
为了减少阅读的干扰和提升阅读服从,不相干的代码省略,同时部分代码会在表明中展示
流程
SSL_do_handshake
- int SSL_do_handshake(SSL *s)
- {
- SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
- /* 步骤【24】
- * 再次进入
- */
- if ((sc->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
- /* 步骤【1】
- * 检测是否开启了异步模式,并且没有正在运行的 job
- * 通过 ASYNC_get_current_job 可获取当前正在运行的 job
- * ASYNC_get_current_job 中通过 async_get_ctx 获取线程变量 ctx
- * ctx 中的字段 currjob 即为返回值
- */
- struct ssl_async_args args;
- memset(&args, 0, sizeof(args));
- args.s = s;
- /* 步骤【2】
- * 通过 ssl_start_async_job 启动一个异步 job
- * 关注一下 args 中赋值的唯一一个参数 s
- *
- * 步骤【25】
- * 通过 ssl_start_async_job 启动一个异步 job
- */
- ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
- /* 步骤【18】
- * 至此通过 ssl_start_async_job 得到了与 else 中一样的返回值
- * 因为提到过 ssl_start_async_job 中最终也是执行的 sc->handshake_func,且执行完成
- *
- * 步骤【23】
- * 返回值为 -1,但此时 job,并没有完成
- * 函数的调用者需要有能力处理这种情况,并且在适当实际继续执行 job
- * 这就需要调用者去适配 OpenSSL 异步模式
- */
- } else {
- ret = sc->handshake_func(s);
- }
- }
复制代码 ssl_start_async_job
- static int ssl_start_async_job(SSL *s, struct ssl_async_args *args,
- int (*func) (void *))
- {
- /* 步骤【3】
- * 启动一个 job
- * sc->job 在首次调用时为 NULL
- * args.s 为 s
- * func 为 ssl_do_handshake_intern
- *
- * 步骤【26】
- */
- switch (ASYNC_start_job(&sc->job, sc->waitctx, &ret, func, args,
- sizeof(struct ssl_async_args))) {
- /* 步骤【16】
- * 返回值为 ASYNC_FINISH
- *
- * 步骤【21】
- * 返回值为 ASYNC_PAUSE
- */
- case ASYNC_ERR:
- sc->rwstate = SSL_NOTHING;
- ERR_raise(ERR_LIB_SSL, SSL_R_FAILED_TO_INIT_ASYNC);
- return -1;
- case ASYNC_PAUSE:
- /* 步骤【22】
- */
- sc->rwstate = SSL_ASYNC_PAUSED;
- return -1;
- case ASYNC_NO_JOBS:
- sc->rwstate = SSL_ASYNC_NO_JOBS;
- return -1;
- case ASYNC_FINISH:
- /* 步骤【17】
- */
- sc->job = NULL;
- return ret;
- default:
- sc->rwstate = SSL_NOTHING;
- ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
- /* Shouldn't happen */
- return -1;
- }
- }
复制代码 ASYNC_start_job
- int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *wctx, int *ret,
- int (*func)(void *), void *args, size_t size)
- {
- /* 步骤【4】
- * ctx 为线程变量,对线程而言全局可见,当前为第一次
- * 会创建 ctx,后续的 job 都能通过 async_get_ctx() 获取
- *
- * 步骤【27】
- * ctx 为线程变量,之前已经创建过,可以直接获取
- */
- ctx = async_get_ctx();
- if (ctx == NULL)
- ctx = async_ctx_new();
- if (ctx == NULL)
- return ASYNC_ERR;
- /* 步骤【5】
- * 首次调用 *job 应为 NULL
- *
- * 步骤【28】
- * job 有值
- */
- if (*job != NULL)
- ctx->currjob = *job;
- for (;;) {
- /* 步骤【6】
- * ctx 刚创建,ctx->currjob 为 NULL,跳过
- *
- * 步骤【14】
- * 此时 ctx->currjob 有值,且 ctx->currjob->status 为 ASYNC_JOB_STOPPING
- *
- * 步骤【29】
- * 此时 ctx->currjob 有值,且 ctx->currjob->status 为 ASYNC_JOB_PAUSED
- */
- if (ctx->currjob != NULL) {
- if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
- /* 步骤【15】
- * job 完成,返回 ASYNC_FINISH
- * 此时,可以释放针对该 job 申请的各种资源
- * 重置线程变量 ctx 上有关的值
- */
- *ret = ctx->currjob->ret;
- ctx->currjob->waitctx = NULL;
- async_release_job(ctx->currjob);
- ctx->currjob = NULL;
- *job = NULL;
- return ASYNC_FINISH;
- }
- if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
- /* 步骤【20】
- * job 暂停,返回 ASYNC_PAUSE
- * 此时,不要释放针对该 job 申请的各种资源
- * 仅仅将 currjob 设置为 NULL
- */
- *job = ctx->currjob;
- ctx->currjob->status = ASYNC_JOB_PAUSED;
- ctx->currjob = NULL;
- return ASYNC_PAUSE;
- }
- if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
- /* 步骤【30】
- * 将 ctx->currjob->fibrectx->fibre 作为目标上下文切换
- * ctx->currjob->fibrectx->fibre 中保存的即为
- * ASYNC_pause_job 中切换上下文时保存的信息
- * 当前上下午的信息保存在 ctx->dispatcher->fibre 中
- */
- if (!async_fibre_swapcontext(&ctx->dispatcher,
- &ctx->currjob->fibrectx, 1)) {
- ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
- ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
- goto err;
- }
- /* 步骤【34】
- * 上下文切换回此,由于 job 已经完成,status 为 ASYNC_JOB_STOPPING
- * 接下来的流程与步骤【15】相同
- */
- continue;
- }
- }
- /* 步骤【7】
- * 由于 job 尚未创建,通过 async_get_pool_job 从池子中获取一个 job
- * 由于 job 中有参数需要关注,所以还要看一下 async_get_pool_job 的实现
- * 具体的 async_get_pool_job 中调用 async_job_new 创建 job
- * 然后调用 async_fibre_makecontext 初始化 job->fibrectx
- * 使用 makecontext(&fibre->fibre, async_start_func, 0),其中的 fibre 即 job->fibrectx
- * makecontext 是 libc 提供的库,与之对应的是 swapcontext
- * makecontext 用于组装上下文信息,swapcontext 用于切换上下文信息
- * 后续有关 async_fibre_makecontext 和 async_fibre_swapcontext 其实就是
- * makecontext 与 swapcontext
- * 当通过 swapcontext 切换上下文,切换的目标为 job->fibrectx->fibre,就会调用 async_start_func
- */
- if ((ctx->currjob = async_get_pool_job()) == NULL)
- return ASYNC_NO_JOBS;
- /* 步骤【8】
- * 将 args,func(ssl_do_handshake_intern)等参数绑定至 ctx->currjob
- */
- if (args != NULL) {
- ctx->currjob->funcargs = OPENSSL_malloc(size);
- if (ctx->currjob->funcargs == NULL) {
- async_release_job(ctx->currjob);
- ctx->currjob = NULL;
- return ASYNC_ERR;
- }
- memcpy(ctx->currjob->funcargs, args, size);
- } else {
- ctx->currjob->funcargs = NULL;
- }
- ctx->currjob->func = func;
- ctx->currjob->waitctx = wctx;
- /* 步骤【9】
- * 至此,job 初始化完成
- * 其中有几个关键参数,再总结一下,即
- * func 为 ssl_do_handshake_intern
- * args 中的 args.s 为 ssl_start_async_job 传入的 s
- * 该 job 也同时被绑定到了线程变量 ctx 的 currjob 上
- * job->fibrectx->fibre 与 async_start_func 绑定
- */
- /* 步骤【10】
- * 这是关键,用于做上下文切换,即上文提到过的
- * swapcontext(&o->fibre, &n->fibre)
- * 第一个参数中的 o 与第二个参数中的 n,分别对应 &ctx->dispatcher 和 &ctx->currjob->fibrectx
- * 第二个参数是目标,而这个目标就是上文反复提到的 job->fibrectx->fibre,因此会调用 async_start_func
- * 第一个参数用来存储当前状态,即会记录此时的上下文信息
- * 当下一次将 ctx->dispatcher 作为 swapcontext 的第二个参数时,就能恢复到此处继续执行
- */
- if (!async_fibre_swapcontext(&ctx->dispatcher,
- &ctx->currjob->fibrectx, 1)) {
- ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
- goto err;
- }
- /* 步骤【13】
- * 在 async_start_func 中完成 job->func 后执行 swapcontext 切换回此
- * 此时的 job->status 值为 ASYNC_JOB_STOPPING
- *
- * 步骤【19】
- * 在 ASYNC_pause_job 中,执行 swapcontext 切换回此
- * 此时的 job->status 值为 ASYNC_JOB_PAUSING
- */
- }
- }
复制代码 async_start_func
- void async_start_func(void)
- {
- async_ctx *ctx = async_get_ctx();
- while (1) {
- /* 步骤【11】
- * 此处所有参数在前文都重点提到过
- * func 为 ssl_do_handshake_intern,参数 args,查看 ssl_do_handshake_intern
- * 实际上,就是调用的 sc->handshake_func
- * 这等同于步骤【1】中未开启异步模式的情况,区别就是现在是启动了一个 job 执行
- */
- job = ctx->currjob;
- job->ret = job->func(job->funcargs);
- /* 步骤【12】
- * job->func 执行完成,即 job 结束
- * 进行上下文切换,将 ctx->dispatcher 作为 swapcontext 的第二个参数
- * 回头看看,就知道切换到哪里了,当前现场保存在 job->fibrectx->fibre 中
- *
- * 步骤【33】
- * 暂停的 job 执行完成,上下文切换到目标 job->fibrectx->fibre
- */
- job->status = ASYNC_JOB_STOPPING;
- if (!async_fibre_swapcontext(&job->fibrectx,
- &ctx->dispatcher, 1)) {
- ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
- }
- }
- }
复制代码 停息
读到此处,不知是否会有这样的疑问?OpenSSL 异步模式,异步体现在哪里?上述步骤中,不就是通过一个新的上下文去实行了 sc->handshake_func 吗?这与步骤【1】的 else 中,直接实行 sc->handshake_func 有什么区别吗?如果你产生了这样的疑问,那就证明上面的步骤你应该是理解了。由于如果按照步骤这样一路看下来,确实不是异步模式,而是同步模式。
这是由于另有一个重要的机制没有介绍,那就是停息,这个机制允许上下文实行到某个地方,切换出去,做别的事情,然后再切换返来必须实行。而这个机制起作用的地方出现在步骤【11】到步骤【12】之间,实行 job->func 中。
思量一种情况,job->func 中,必要进行签名验签,加密解密,而这些算法是卸载到硬件的,调用算法必要等待硬件返回,而在这一段时间中,只能壅闭,导致 CPU 利用率低,例如如下代码。- void do_sign()
- {
- // 获得一个硬件签名请求结构体并初始化
- struct req *r = sign_req_alloc();
- sign_req_init(r);
- // 利用硬件 API 发起签名请求
- // sign 中仅仅传递请求,就返回了,剩下就交给硬件
- sign(r);
- // 当硬件完成任务,会将 r->done 变为 true
- // 此处阻塞,不断的轮询 r->done 是否变为 true 来检测任务是否完成
- while (1) {
- if (r->done) {
- break;
- }
- usleep(0);
- }
- // 完成签名,退出
- return;
- }
复制代码 可以看出,如果硬件没有完成使命,就会一直实行 while(1),无法退出,导致无法处置惩罚其他使命。如果硬件性能很高,壅闭时间很短,可能影响会比力小,但是,如果硬件性能较差,壅闭时间长,就会导致 CPU 利用率很低。此时就可以使用 OpenSSL 异步模式中提供的 ASYNC_pause_job,如下代码。- void do_sign()
- {
- // 获得一个硬件签名请求结构体并初始化
- struct req *r = sign_req_alloc();
- sign_req_init(r);
- // 利用硬件 API 发起签名请求
- // sign 中仅仅传递请求,就返回了,剩下就交给硬件
- sign(r);
- ASYNC_pause_job();
- /* 步骤【32】
- * do_sign 完成退出,会一直退到 async_start_func
- */
- // 完成签名,退出
- return;
- }
复制代码 仅仅将必要壅闭等待的代码,更换为 ASYNC_pause_job 即可。
ASYNC_pause_job
- int ASYNC_pause_job(void)
- {
- job = ctx->currjob;
- job->status = ASYNC_JOB_PAUSING;
- /* 上下文切换,会切换到步骤【13】的地方继续执行
- * 需要注意的是,job 的状态设置成了 ASYNC_JOB_PAUSING
- * 并且将当前的上下文保存在 job->fibrectx->fibre 中
- * 紧接着步骤【19】
- */
- if (!async_fibre_swapcontext(&job->fibrectx,
- &ctx->dispatcher, 1)) {
- ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
- return 0;
- }
- /* 步骤【31】
- * 上下文切换之后,继续执行,就回到了 do_sign 中
- */
- }
复制代码 恢复
前文提到过,调用者必要有本领处置惩罚 SSL_do_handshake 返回值 -1,但 job 还存在的情况,这意味着 SSL_do_handshake 中通过 ASYNC_pause_job 停息的 job。
而调用者必要在一个合适的机遇,去继续运行该 job,怎样确定什么时候是合适的机遇,这是调用者必要思量的题目,此处仅介绍怎样恢复 job 的实行,很简单,再次调用 SSL_do_handshake 即可。
回到流程中,此时从步骤【24】开始。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |