帮我生成一个使用openssl3.0+ provider算法异步调用的示例
关于 OpenSSL 异步框架的"关键认知"
在看代码之前,有一个关于 OpenSSL 异步机制非常重要且容易误解的点需要说明:
仅仅把一个普通的计算函数(比如算 SHA256)塞进 ASYNC_start_job 里,它并不会自动变成异步非阻塞的。
OpenSSL 的异步机制基于协程/线程(Fibers/ucontext)。它的工作原理是:
- 主线程调用
ASYNC_start_job进入协程。 - 协程内部的代码开始执行。
- 关键点: 只有当协程内部的代码(通常是底层的硬件 Provider)主动调用
ASYNC_pause_job()时,协程才会挂起,控制权才会返回给主线程(此时start_job返回ASYNC_PAUSE)。 - 主线程去干别的事情。
- 当硬件完成操作,主线程再次调用
ASYNC_start_job,协程会从上次暂停的地方恢复执行。
默认的纯软件加密算法(如默认 Provider 里的 SHA256)是不会 调用 ASYNC_pause_job() 的。它们会一口气算完,导致 start_job 依然是阻塞的。
为了在这个演示程序中体现出"异步交替执行"的效果,我会在包装函数中人为地调用 ASYNC_p ause_job() 来模拟硬件卸载的延迟。
完整代码 (test_md_async.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/provider.h>
#include <openssl/async.h>
#include <openssl/err.h>
// 传递给协程的参数结构体
struct async_args {
EVP_MD_CTX *mdctx;
unsigned char *digest;
unsigned int *digest_len;
};
// =========================================================================
// 这是将在 OpenSSL 协程(Fiber)中执行的工作函数
// =========================================================================
static int async_job_wrapper(void *arg) {
struct async_args *args = (struct async_args *)arg;
printf(" [Fiber] 协程开始执行...\n");
// 【模拟异步硬件操作】
// 真实的异步 Provider (例如 Intel QAT) 会在这里向硬件发送指令,
// 获取一个文件描述符(fd),并调用 ASYNC_pause_job() 挂起当前协程。
printf(" [Fiber] 模拟调用外部硬件... 挂起当前协程,将控制权交还主线程。\n");
// 主动挂起协程 (非常重要,否则就变成同步阻塞了)
ASYNC_pause_job();
printf(" [Fiber] 协程被唤醒!硬件操作完成,继续执行。\n");
// 执行实际的加密计算操作 (这里使用软件 SHA256 替代硬件计算)
int ret = EVP_DigestFinal_ex(args->mdctx, args->digest, args->digest_len);
printf(" [Fiber] 协程执行完毕。\n");
return ret; // 这个返回值会被传递给主线程
}
// 辅助打印函数
void print_hex(const char* label, const unsigned char* data, size_t len) {
printf("%s: ", label);
for (size_t i = 0; i < len; ++i) {
printf("%02x", data[i]);
}
printf("\n");
}
int main() {
OSSL_LIB_CTX *libctx = NULL;
OSSL_PROVIDER *provider = NULL;
EVP_MD *sm3 = NULL;
EVP_MD_CTX *mdctx = NULL;
// 异步相关的变量
ASYNC_JOB *job = NULL;
ASYNC_WAIT_CTX *waitctx = NULL;
const unsigned char message[] = "Hello OpenSSL 3 Async Framework!";
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = 0;
int job_ret = 0;
int status;
int ret_code = 1;
printf("1. 初始化 OpenSSL 3.0 上下文和 Provider\n");
libctx = OSSL_LIB_CTX_new();
provider = OSSL_PROVIDER_load(libctx, "default");
if (!provider) {
fprintf(stderr, "加载 provider 失败\n");
goto cleanup;
}
// 初始化异步环境 (当前线程)
if (!ASYNC_init_thread(1, 0)) {
fprintf(stderr, "初始化异步线程失败\n");
goto cleanup;
}
printf("2. 准备 SM3 摘要操作\n");
sm3 = EVP_MD_fetch(libctx, "SM3", NULL);
mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, sm3, NULL);
EVP_DigestUpdate(mdctx, message, strlen((const char *)message));
// 打包参数
struct async_args args = { mdctx, digest, &digest_len };
// 创建等待上下文 (必须有)
waitctx = ASYNC_WAIT_CTX_new();
if (!waitctx) goto cleanup;
printf("3. 启动异步作业 (进入状态机)...\n");
printf("--------------------------------------------------\n");
// 【主循环:驱动异步状态机】
while (1) {
printf("[Main] 调用 ASYNC_start_job...\n");
// 启动或恢复协程
status = ASYNC_start_job(&job, waitctx, &job_ret, async_job_wrapper, &args, sizeof(args));
if (status == ASYNC_PAUSE) {
// 协程内部调用了 ASYNC_pause_job()
printf("[Main] 作业已挂起 (ASYNC_PAUSE)。\n");
printf("[Main] --> 此时主线程是非阻塞的,可以去处理网络事件或执行其他任务 <--\n");
// 在实际的高性能应用中,这里会通过 epoll 获取 waitctx 中的 FD 并等待。
// 这里的演示我们直接继续下一次循环(即立即唤醒它)。
} else if (status == ASYNC_FINISH) {
// 协程内部的函数 return 了
printf("[Main] 作业已完成 (ASYNC_FINISH)。\n");
break;
} else if (status == ASYNC_ERR) {
fprintf(stderr, "[Main] 作业执行出错 (ASYNC_ERR)。\n");
ERR_print_errors_fp(stderr);
goto cleanup;
}
}
printf("--------------------------------------------------\n");
printf("4. 获取最终结果 (协程返回值: %d)\n", job_ret);
if (job_ret == 1) {
print_hex("Message", message, strlen((const char *)message));
print_hex("SM3", digest, digest_len);
ret_code = 0;
}
cleanup:
// 清理资源
if (waitctx) ASYNC_WAIT_CTX_free(waitctx);
// ASYNC_JOB 通常会在 FINISH 后自动释放,无需手动 free
ASYNC_cleanup_thread(); // 清理当前线程的异步环境
if (mdctx) EVP_MD_CTX_free(mdctx);
if (sm3) EVP_MD_free(sm3);
if (provider) OSSL_PROVIDER_unload(provider);
if (libctx) OSSL_LIB_CTX_free(libctx);
return ret_code;
}
Makefile
cpp
CC = gcc -g
CFLAGS = -fPIC -Wall -I/usr/include
LIBS = -lcrypto -L.
all: test_md_async
test_md_async: test_md_async.o
$(CC) -o $@ $^ $(LIBS) -Wl,-rpath,.
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o test_app
运行后结果
bash
$ make
$ ./test_md_async
1. 初始化 OpenSSL 3.0 上下文和 Provider
2. 准备 SM3 摘要操作
3. 启动异步作业 (进入状态机)...
--------------------------------------------------
[Main] 调用 ASYNC_start_job...
[Fiber] 协程开始执行...
[Fiber] 模拟调用外部硬件... 挂起当前协程,将控制权交还主线程。
[Main] 作业已挂起 (ASYNC_PAUSE)。
[Main] --> 此时主线程是非阻塞的,可以去处理网络事件或执行其他任务 <--
[Main] 调用 ASYNC_start_job...
[Fiber] 协程被唤醒!硬件操作完成,继续执行。
[Fiber] 协程执行完毕。
[Main] 作业已完成 (ASYNC_FINISH)。
--------------------------------------------------
4. 获取最终结果 (协程返回值: 1)
Message: 48656c6c6f204f70656e53534c2033204173796e63204672616d65776f726b21
SM3: 8c1408025467f1ea3db8b40d4858d9b9df25178bc70bed8c544e2adf409e6cb7