[classic_tong: https://www.cnblogs.com/hugetong/p/14379347.html]
这一部分内容的前面还有个第一部分,可以作为接下来内容的前提或者背景进行阅读:[openssl] intel qat场景下的openssl框架
引言
openssl1.1.1版本新增了async功能。是为了更好支持qat卡新增的功能。
详见:[openssl] intel qat场景下的openssl框架 有关性能部分的讨论。
API
回顾前文我们讨论了openssl的基本框架,包含:app,ssl,crypto,engines四部分。
async模块是crypto component中的一部分。它目前仅被ssl模块引用,被封装在SSL的api调用下面
sync模块的主要API:
/* 提供给应用程序的接口, */
int ASYNC_init_thread(size_t max_size, size_t init_size); void ASYNC_cleanup_thread(void); int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *ctx, int *ret, int (*func)(void *), void *args, size_t size);
/* 提供给应用程序用于epoll的接口。 */ ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job); int ASYNC_WAIT_CTX_get_fd(ASYNC_WAIT_CTX *ctx, const void *key, OSSL_ASYNC_FD *fd, void **custom_data); int ASYNC_WAIT_CTX_get_all_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *fd, size_t *numfds); int ASYNC_WAIT_CTX_get_changed_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *addfd, size_t *numaddfds, OSSL_ASYNC_FD *delfd, size_t *numdelfds); /* 对ENGINE的接口,在ENGINE中调用 */ ASYNC_WAIT_CTX *ASYNC_WAIT_CTX_new(void); void ASYNC_WAIT_CTX_free(ASYNC_WAIT_CTX *ctx); int ASYNC_WAIT_CTX_set_wait_fd(ASYNC_WAIT_CTX *ctx, const void *key, OSSL_ASYNC_FD fd, void *custom_data, void (*cleanup)(ASYNC_WAIT_CTX *, const void *, OSSL_ASYNC_FD, void *));
int ASYNC_pause_job(void);
SSL模块的主要API
/* SSL的基本IO接口 */
SSL *SSL_new(SSL_CTX *ctx); void SSL_free(SSL *ssl);
int SSL_accept(SSL *ssl);
int SSL_connect(SSL *ssl);
int SSL_read(SSL *ssl, void *buf, int num);
int SSL_peek(SSL *ssl, void *buf, int num);
int SSL_write(SSL *ssl, const void *buf, int num);
/* SSL 用于异步处理的 socket fd */
int SSL_get_fd(const SSL *s);
/* SSL 用于异步处理的加解密设备 fd */
int SSL_waiting_for_async(SSL *s);
int SSL_get_all_async_fds(SSL *s, OSSL_ASYNC_FD *fds, size_t *numfds);
int SSL_get_changed_async_fds(SSL *s, OSSL_ASYNC_FD *addfd,
size_t *numaddfds, OSSL_ASYNC_FD *delfd, size_t *numdelfds);
ENGINE注册进openssl的接口
ENGINE_set_id(e, engine_qat_id)
ENGINE_set_name(e, engine_qat_name)
ENGINE_set_RSA(e, qat_get_RSA_methods())
ENGINE_set_DSA(e, qat_get_DSA_methods())
ENGINE_set_DH(e, qat_get_DH_methods())
ENGINE_set_EC(e, qat_get_EC_methods())
ENGINE_set_pkey_meths(e, qat_pkey_methods)
ENGINE_set_RSA(e, multibuff_get_RSA_methods())
ENGINE_set_pkey_meths(e, multibuff_x25519_pkey_methods)
ENGINE_set_ciphers(e, qat_ciphers)
上下文切换
async为了达到并行的目的,在单线程中实现了协程。请读:[openssl] 协程
async模块中,将其抽象为job,job为一次运行即结束的。多个job同时运行,使用协程进行调度。
job有个pool,这个pool是堆内存,地址存储在每线程的全局变量里,使用api pthread_getspecfic()
核心的协程切换在这个函数里:https://github.com/openssl/openssl/blob/master/crypto/async/arch/async_posix.h::async_fibre_swapcontext()
封装
这一小节讲,上面的三部分接口是怎么结合起来的。主要包括ssl怎么与async结合,engine怎么对async支持。
ssl与async
ssl内部组织了一个状态机,将握手,读写等操作抽象为两个job,ssl_io_intern(读写), ssl_do_handshake_intern(握手), 统一通过api ASYNC_start_job()进行job调度。
JOB有 ASYNC_ERR, ASYNC_PAUSE, ASYNC_FINISH, ASYNC_NO_JOBS, 四个状态。状态表征了job的运行情况。
async的基本模型是,1 cpu进行用户应用的前期计算,2 cpu进行ssl框架内的前期计算, 3 write数据进qat卡,4 qat卡计算,5 read数据出qat卡,6 cpu进行ssl框架内的后期计算,7 cpu进行用户应用的后期计算。
async的目标就是让cpu在阶段3进行有效计算。
为了达到这个目标,async模块将流程2356划分为一个job,17作为主流程(主协程?主job)。4被卸载到了硬件卡上,不占用cpu。协程的本质是对cpu的分时复用,所以,我们讨论job的时候,就是在讨论cpu的时间片分配。
ssl调用了async_start_job之后,便进入了job 2356流程,当job执行完23之后,会调用api ASYNC_pause_job() 切换回主job,并将job状态设置为ASYNC_PAUSE, 这个时候CPU会交还给主job完全其他计算工作,qat并行的进行他自己的计算。
主job知道另一个job没有进入结束状态,所以它利用SSL_waiting_for_async()接口获得的一个eventfd,并将它epoll()起来。当qat卡计算完成(另一个问题,cpu怎么知道qat卡计算完成了?)会执行一个回调,这个回调会写入eventfd,从而
主job再次进入ssl之前的api,继续job的56流程。完成6之后,再调用ASYNC_pause_job()切换主job,并将job状态设置为ASYNC_FINISH. 主job完成流程7. 结束。
async与engine
接上面的问题, cpu怎么知道qat卡完成了计算?
答案:文章[openssl] intel qat场景下的openssl框架中提到了四种模式,其中模式inline启动了一个线程,该线程在不停的调用qatdriver的polling api,轮训获取qat的计算状态,得到相应结果后,写入eventfd,唤醒job。
其他基于timer的模式,机制类似。
前文提到的流程2(一部分)356(一部分),都被封装在了以插件的形式注册的加解密函数中,如前文的api表格所示。
这部分处理除了负责读写硬件,还有另外两个职责,1, job的切回,也就是pause。2,eventfd的唤醒。
千言万语抵不过一张图,通过下图理解一下:
性能
最后回到上一篇提到的性能问题。
先回顾一下关于qat性能的讨论。 性能的提升有两个角度。
1. 用户调用openssl时使用异步IO,就是使用epoll,select等,完成多个SSL的操作,这个与常规典型的异步IO使用的方法一下,不在赘述。
2. 一个ssl流程里都包括,算随机数,做秘钥协商,非对称加密,对每个block的对称加密,对每个block的对称解密,这一个序列的每一个步都可以由qat卡完成计算。
他们每一个被抽象成一个job,共同并行。这个并行包括了多个qat卡bank和cpu,他们多者之间的并行。
也就是说,如上图所示的同一个SSL_accept(1st)的调用里,可以有多个ASYNC_start_job().
而性能优化的角度1是:如上图的event_loop()的地方,可以有多个SSL_accept(1st)在等待完成。
--------
完