介绍
TLS协议处理加解密是一个非常耗时的过程,尤其是非对称加解密。
如果openssl支持异步化处理,势必能够提升证书卸载效率,openssl从1.1.0版本开始支持异步处理。
openssl要真正发挥TLS异步模式的优势,一个支持异步模式的OpenSSL引擎是不可或缺的。openssl qat引擎是一个全面支持openssl异步模式的的杰出的解决方案。
linux平台openssl对异步模式的实现是基于协程的,由glibc提供。
当TLS被设置成异步模式时,一个异步任务结构就会被分配给该连接。
当一个协程放弃系统资源切换出去的时候,该异步任务被暂停。
旧函数堆栈信息会被完整地保存在这个结构体里。
当协程切换回来再次执行的时候,该异步任务恢复,函数堆栈会从这个结构体里复原出来。
如果这个TLS异步处理请求完整结束,该异步任务结构就会被释放归还。
asyn mode是openssl的异步I/O(AIO)模式,在这个模式下openssl会把硬件加速卡等不占用cpu的操作剥离出来,单独交给一个叫asyn job的结构去做。
在asyn job执行时cpu可以把当前任务暂停,切换上下文(保存/恢复栈、寄存器等)返回给user(用__setjump longjump实现)。
user需要主动或等待硬件加速卡的事件通知,去epoll这个async job的状态,是否是ASYNC_FINISHED状态。
如果是说明之前的任务已经完成,可以继续后面的操作取回加解密结果。
相关Linux API———ucontext族函数
Linux C中的协程主要使用ucontext族函数来实现,在使用感受上类似于经常使用的goto语句(ucontext族函数可以跨函数)。
man手册中对makecontext,swapcontext函数进行如下描述。
NAME
makecontext, swapcontext - manipulate user context
SYNOPSIS
#include <ucontext.h>
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
DESCRIPTION
In a System V-like environment, one has the type ucontext_t defined in <ucontext.h> and the four functions getcontext(3), setcontext(3), makecontext() and swapcon‐text() that allow user-level context switching between multiple threads of control within a process.
For the type and the first two functions, see getcontext(3).
The makecontext() function modifies the context pointed to by ucp (which was obtained from a call to getcontext(3)). Before invoking makecontext(), the caller must allocate a new stack for this context and assign its address to ucp->uc_stack, and define a successor context and assign its address to ucp->uc_link.
When this context is later activated (using setcontext(3) or swapcontext()) the function func is called, and passed the series of integer (int) arguments that follow
argc; the caller must specify the number of these arguments in argc. When this function returns, the successor context is activated. If the successor context pointer is NULL, the thread exits.
The swapcontext() function saves the current context in the structure pointed to by oucp, and then activates the context pointed to by ucp.
NAME
setjmp, sigsetjmp - save stack context for nonlocal goto
SYNOPSIS
#include <setjmp.h>
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
setjmp(): see NOTES.
sigsetjmp(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_C_SOURCE
DESCRIPTION
setjmp() and longjmp(3) are useful for dealing with errors and interrupts encountered in a low-level subroutine of a program. setjmp() saves the stack context/envi‐ronment in env for later use by longjmp(3). The stack context will be invalidated if the function which called setjmp() returns.
sigsetjmp() is similar to setjmp(). If, and only if, savesigs is nonzero, the process's current signal mask is saved in env and will be restored if a siglongjmp(3)
is later performed with this env.
RETURN VALUE
setjmp() and sigsetjmp() return 0 if returning directly, and nonzero when returning from longjmp(3) or siglongjmp(3) using the saved context.
NAME
longjmp, siglongjmp - nonlocal jump to a saved stack context
SYNOPSIS
#include <setjmp.h>
void longjmp(jmp_buf env, int val);
void siglongjmp(sigjmp_buf env, int val);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
siglongjmp(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_C_SOURCE
DESCRIPTION
longjmp() and setjmp(3) are useful for dealing with errors and interrupts encountered in a low-level subroutine of a program. longjmp() restores the environment
saved by the last call of setjmp(3) with the corresponding env argument. After longjmp() is completed, program execution continues as if the corresponding call of
setjmp(3) had just returned the value val. longjmp() cannot cause 0 to be returned. If longjmp() is invoked with a second argument of 0, 1 will be returned instead.
siglongjmp() is similar to longjmp() except for the type of its env argument. If, and only if, the sigsetjmp(3) call that set this env used a nonzero savesigs flag,
siglongjmp() also restores the signal mask that was saved by sigsetjmp(3).
RETURN VALUE
These functions never return.
static ossl_inline int async_fibre_swapcontext(async_fibre *o, async_fibre *n, int r)
{
o->env_init = 1;
if (!r || !_setjmp(o->env)) {
if (n->env_init)
_longjmp(n->env, 1);
else
setcontext(&n->fibre);
}return 1;
}
async_fibre_swapcontext函数用来进行协程的切换,对于传进来的o和n分别代表需要用来保存的上下文和需要去跳转的上下文,对于每一个需要保存的上下文中的env_init赋值为1,表示当前上下文被setjmp进行保存,下次要使用_longjmp进行跳转。
对于当前执行的上下文使用 _setjmp保存到o->env中, _setjmp的返回值由 _longjmp第二个参数决定,也就是说当执行过 _longjmp之后, ! _setjmp(o->env)的返回值为0,不会再一次进行跳转,这样就避免了函数陷入死循环。
之后会进行n->env_init的判断,前面讲过当前环境如果执行过async_fibre_swapcontext,env_init必然会被赋值为1,所以当前需要跳转的上下文没有执行过async_fibre_swapcontext就会执行setcontext,对应的是makecontext中设置的函数入口。
这样就完成了一次协程之间的跳转。
下面看一下openssl是如何使用这几个函数的。
关于setjmp保存当前函数执行的上下文,使用longjmp跳转指定的上下文环境。
并且setjmp的返回值由longjmp中的第二个参数决定。
makecontext是指定一个函数入口的上下文,把这个函数入口的上下文保存在ucontext_t ucp 上下文,当下次跳转(setcontext或者swapcontext)这个ucp的上下文时,将执行makecontext中指定的函数。setcontext和swapcontext用来对指定的上下文进行跳转。
数据结构
ASYNC_JOB: fibrectx用来保存和恢复栈、寄存器;waitctx指向SSL的waitctx。
async_ctx: 全局唯一,currjob指向一个ASYNC_JOB;dispatcher用来保存和恢复栈、寄存器,与ASYNC_JOB的fibrectx配合使用。
Openssl ASYNC Mode运行流程
Openssl ASYNC Mode运行流程分为三个层面,在app层面调用ASYNC_start_job函数,将要用户需要执行的函数以及参数传入到ASYNC_start_job里面。
因为是异步调用,当前仅仅是通知底层要进行执行该函数,所以会立即返回一个ASYNC_PAUSE状态,app层面将会调用epoll_wait等待下层执行结果。
当执行ASYNC_start_job后,openssl会取出一个job作为currjob来保存函数运行时的各种参数和协程的上下文变量。
一个新的work第一次执行时,将执行makecontext中的函数async_start_fun,并调用async_fibre_swapcontext保存当前上下文跳转到async_start_fun。
这个时候async_start_fun会调用QAT_Engine中实际进行工作的函数,第一次执行时,调用ASYNC_pause_job函数,通知上层应用,计算任务还没有完成,并完记录函数执行位置,然后将控制权交给上层应用。