一、Async mode的功能
Asyn mode是OpenSSL支持異步I/O(AIO)的模式,在這個模式下openssl把硬件加速卡等不佔用cpu的操作剝離出來,單獨交給一個叫asyn job的結構去做。在asyn job執行的過程中,cpu可以把當前任務暫停,切換上下文(保存/恢復棧,寄存器等,用__setjump, longjump實現)返回給user。User需要主動(或者等待硬件加速卡的事件通知)去poll這個async job的狀態,是否是ASYNC_FINISHED狀態。如果是,說明之前的任務已經完成,則可以繼續後面的操作(取回加密/解密結果)。
二、Async相關數據結構
比較重要的數據結構:
ASYNC_JOB: fibrectx用來保存和恢復棧、寄存器;waitctx指向SSL的waitctx。
async_ctx: 全局唯一,currjob指向一個ASYNC_JOB;dispatcher用來保存和恢復棧、寄存器,與ASYNC_JOB的fibrectx配合使用。
三、Async mode相關API
開啓Async mode可以使用:SSL_CTX_set_mode(ctx, SSL_MODE_ASYNC)或SSL_set_mode(ssl, SSL_MODE_ASYNC)。在user調用SSL_do_handshake()(SSL_read()/SSL_write()類似)時,會調用到ssl_start_async_job():
3578 int SSL_do_handshake(SSL *s)
3579 {
3580 int ret = 1;
3581
3582 if (s->handshake_func == NULL) {
3583 SSLerr(SSL_F_SSL_DO_HANDSHAKE, SSL_R_CONNECTION_TYPE_NOT_SET);
3584 return -1;
3585 }
3586
3587 ossl_statem_check_finish_init(s, -1);
3588
3589 s->method->ssl_renegotiate_check(s, 0);
3590
3591 if (SSL_in_init(s) || SSL_in_before(s)) {
3592 if ((s->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
3593 struct ssl_async_args args;
3594
3595 args.s = s;
3596
3597 ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
3598 } else {
3599 ret = s->handshake_func(s);
3600 }
3601 }
3602 return ret;
3603 }
ASYNC_get_current_job()就是返回全局的async_ctx->currjob,如果不爲NULL意味着當前有一個job正在處理中,不應該在開啓另外一個job;如果s->handshake_func觸發的回調函數沒有再訪問一個SSL連接的話,只有在多線程環境下才會進入else分支(3599行)。
ssl_start_async_job()會調用ASYNC_start_job()函數處理job,回調函數是ssl_do_handshake_intern,其實就是s->handshake_func的簡單包裹。
168 int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *wctx, int *ret,
169 int (*func)(void *), void *args, size_t size)
170 {
171 async_ctx *ctx;
172
173 if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
174 return ASYNC_ERR;
175
176 ctx = async_get_ctx();
177 if (ctx == NULL)
178 ctx = async_ctx_new();
179 if (ctx == NULL)
180 return ASYNC_ERR;
181
182 if (*job)
183 ctx->currjob = *job;
184
185 for (;;) {
186 if (ctx->currjob != NULL) {
187 if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
188 *ret = ctx->currjob->ret;
189 ctx->currjob->waitctx = NULL;
190 async_release_job(ctx->currjob);
191 ctx->currjob = NULL;
192 *job = NULL;
193 return ASYNC_FINISH;
194 }
195
196 if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
197 *job = ctx->currjob;
198 ctx->currjob->status = ASYNC_JOB_PAUSED;
199 ctx->currjob = NULL;
200 return ASYNC_PAUSE;
201 }
202
203 if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
204 ctx->currjob = *job;
205 /* Resume previous job */
206 if (!async_fibre_swapcontext(&ctx->dispatcher,
207 &ctx->currjob->fibrectx, 1)) {
208 ASYNCerr(ASYNC_F_ASYNC_START_JOB,
209 ASYNC_R_FAILED_TO_SWAP_CONTEXT);
210 goto err;
211 }
212 continue;
213 }
214
215 /* Should not happen */
216 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_INTERNAL_ERROR);
217 async_release_job(ctx->currjob);
218 ctx->currjob = NULL;
219 *job = NULL;
220 return ASYNC_ERR;
221 }
222
223 /* Start a new job */
224 if ((ctx->currjob = async_get_pool_job()) == NULL)
225 return ASYNC_NO_JOBS;
226
227 if (args != NULL) {
228 ctx->currjob->funcargs = OPENSSL_malloc(size);
229 if (ctx->currjob->funcargs == NULL) {
230 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_MALLOC_FAILURE);
231 async_release_job(ctx->currjob);
232 ctx->currjob = NULL;
233 return ASYNC_ERR;
234 }
235 memcpy(ctx->currjob->funcargs, args, size);
236 } else {
237 ctx->currjob->funcargs = NULL;
238 }
239
240 ctx->currjob->func = func;
241 ctx->currjob->waitctx = wctx;
242 if (!async_fibre_swapcontext(&ctx->dispatcher,
243 &ctx->currjob->fibrectx, 1)) {
244 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
245 goto err;
246 }
247 }
248
249 err:
250 async_release_job(ctx->currjob);
251 ctx->currjob = NULL;
252 *job = NULL;
253 return ASYNC_ERR;
254 }
第一次調用時ctx->currjob爲NULL,會調用async_get_pool_job()申請一個job,在242-243行調用async_fibre_swapcontext()時會觸發async_start_func()函數:
144 void async_start_func(void)
145 {
146 ASYNC_JOB *job;
147 async_ctx *ctx = async_get_ctx();
148
149 while (1) {
150 /* Run the job */
151 job = ctx->currjob;
152 job->ret = job->func(job->funcargs);
153
154 /* Stop the job */
155 job->status = ASYNC_JOB_STOPPING;
156 if (!async_fibre_swapcontext(&job->fibrectx,
157 &ctx->dispatcher, 1)) {
158 /*
159 * Should not happen. Getting here will close the thread...can't do
160 * much about it
161 */
162 ASYNCerr(ASYNC_F_ASYNC_START_FUNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
163 }
164 }
165 }
152行調用的就是ssl_do_handshake_intern()函數,也就是說在切換了執行上下文後再執行handshake的實際動作;
在調用到密碼算法相關函數(如:RSA 加密/解密)時,這個操作需要提交硬件加速卡來執行,提交請求完畢後需要等待硬件返回結果,這時需要調用ASYNC_pause_job()函數來結束本次SSL_do_handshake()的調用:
255 int ASYNC_pause_job(void)
256 {
257 ASYNC_JOB *job;
258 async_ctx *ctx = async_get_ctx();
259
260 if (ctx == NULL
261 || ctx->currjob == NULL
262 || ctx->blocked) {
263 /*
264 * Could be we've deliberately not been started within a job so this is
265 * counted as success.
266 */
267 return 1;
268 }
269
270 job = ctx->currjob;
271 job->status = ASYNC_JOB_PAUSING;
272
273 if (!async_fibre_swapcontext(&job->fibrectx,
274 &ctx->dispatcher, 1)) {
275 ASYNCerr(ASYNC_F_ASYNC_PAUSE_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
276 return 0;
277 }
278 /* Reset counts of added and deleted fds */
279 async_wait_ctx_reset_counts(job->waitctx);
280
281 return 1;
282 }
執行到273-274行時,保存當前上下文(async_start_func()函數),返回到ASYNC_start_job()的242-243行,然後在200行返回。
當硬件加速卡完成任務,通知user之後,user會再次調用SSL_do_handshake(),同樣還會進入到ASYNC_start_job()這時會進入203行這個分支,並在206行調用async_fibre_swapcontext()時切換到之前保存的async_start_func()函數的152行(即s->handshake_func)函數內部調用的ASYNC_pause_job()函數的273-274行,然後執行279行並退出這個函數,取回硬件加密/解密的結果,執行後續處理(構建handshake消息等);接下來async_start_func()函數的152行結束在156-157行調用async_fibre_swapcontext()切換回ASYNC_start_job()函數的206-207行,最後通過187行這個分支在193行返回ASYNC_FINISH。
總結下ASYNC_JOB的狀態變遷流程:
第一次handshake: ASYNC_start_job()->async_get_pool_job()--->[ASYNC_JOB_RUNNING]--->async_fibre_swapcontext()-> async_start_func()->ASYNC_pause_job()--->[ASYNC_JOB_PAUSING]--->async_fibre_swapcontext->ASYNC_start_job()---> [ASYNC_JOB_PAUSED]
第二次handshake: [ASYNC_JOB_PAUSED]--->ASYNC_start_job()->async_fibre_swapcontext()->async_start_func()---> [ASYNC_JOB_STOPPING]--->async_fibre_swapcontext()->ASYNC_start_job()--->return ASYNC_FINISH
四、QAT engine ASYNC運行流程
本圖以RSA加密解密爲例,簡要介紹下intel QAT engine的ASYNC mode流程。