OpenSSL-Async mode

一、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流程。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章