[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)在等待完成。
--------
完