crypto: eliminate fallback proxy overhead while disabled
💡 一句话总结
在 crypto 硬件加速器(如 EIP93)默认关闭动态软件回退的形态下,旧实现把回退代理算法常驻注册进 Crypto API,导致新分配 transform 即使禁用也会解析到代理层、每个请求多付一次阈值判断与间接派发开销。补丁把代理注册/注销与 per-device 的 enabled 开关绑定:禁用时注销代理使新 transform 直连硬件,启用时再注册并用 kref 保留退役代理直到旧 transform 释放。效果:默认关闭态硬件热路径代理开销归零(补丁未单独量化;系列数据指示代理在 4096B 大请求上较直连约 +4.4% 周期,作者自报,未独立验证),而小请求启用后经软件路径 16B 周期下降 94.4%(作者自报,未独立验证)。
📋 补丁基本信息
| 项目 | 内容 |
|---|---|
| 补丁类型 | 优化(性能 · 消除特性默认关闭态的热路径代理开销;属"动态软件回退"新特性系列的性能收尾补丁) |
| 性能类别 | 热路径(禁用态移除一层阈值判断 + 间接派发;免去无用的代理包装层) |
| 状态 | In Review(v1 首版,截至 Linux 7.2-rc6 未合入本地内核 git) |
| 当前版本 | v1(4/4)· 当前版链接 |
| 版本演进 | 首版,暂无演进(2026-07-28 首次提交;截至 2026-08-03 lore 归档无后续版本/回复) |
| 作者机构 | Jihong Min(hurryman2212@gmail.com,个人开发者,无机构标注) |
| 提交日期 | 2026-07-28 |
| 改动范围 | crypto/fallback.c,1 文件,+214/-70(commit message 标注 284 行变更) |
| 核心函数 | fallback_enabled_store() / fallback_register_algs() / fallback_unregister_algs() / fallback_alg_destroy() / crypto_fallback_register() / crypto_fallback_unregister() |
| 原始链接 | lore Message-ID |
系列背景:本补丁是 PATCH 0/4「crypto: introduce generic dynamic software fallback and EIP93 support」 的第 4 篇。前 3 篇依次为:① 移出 tcrypt 的周期基准辅助函数、② 引入动态软件回退框架(crypto/fallback.c 本体,+1441 行)、③ EIP93 驱动接入。本补丁 4/4 是针对框架本身的"禁用态开销"优化。
📊 速览卡片
🎯 解决什么问题
crypto_fallback_register() 在驱动注册时就把所有代理算法注册进 Crypto API,与 enabled 状态无关;"禁用"只是把阈值清零(强制走硬件)。但代理(一个包装算法)仍留在 Crypto API 命名空间里——按名字解析的新 transform 可能命中代理层,每请求仍要多走一次"读阈值 → 比较 size → 间接派发"。
🧩 核心机制
补丁把代理 provider 的生命周期从"注册时全量常驻"改为"跟随 enabled 开关按需注册/注销",并用 kref + 每启用周期独立分配来保证重复开关与旧 transform 的安全。
crypto_fallback_register() 既分配结构体又注册全部代理;新实现只校验参数 + 分配 struct crypto_fallback(把 struct fallback_alg *algs 静态数组改成 list_head algs + spinlock_t alg_lock 动态链表),代理注册推迟到启用时。这直接兑现"禁用时命名空间里没有代理"。
② 开关点联动注册/注销。
fallback_enabled_store()(sysfs enabled 属性写回调)在启用且当前未启用时调 fallback_register_algs(),为每个 group 分配并注册代理;禁用时置 enabled=false、阈值清零、调 fallback_unregister_algs() 注销全部已注册代理。每个启用周期分配全新的 fallback_alg 对象("generation"),与旧代互不干扰。
③ 退役代用 cra_destroy 延迟释放。Crypto API 对每个 crypto_alg 有引用计数,绑定到 transform 的算法不能随手释放。补丁把每个代理的
cra_destroy 指向 fallback_*_destroy:当最后一个使用该代理的 transform 释放、算法引用计数归零时,内核回调 crypto_destroy_alg() → cra_destroy → fallback_alg_destroy(),把代理标记 dead、从链表移除、kref_put 释放,同时 drop 掉 fallback 结构体的引用。于是"从命名空间注销(新查找跳过)"与"对象存活到旧 transform 释放"可以安全并存。
来源:基于 lore 真实补丁 diff + 系列 cover letter 的 benchmark 数据绘制
| 步骤 | 操作 | 目的 |
|---|---|---|
| 注册时 | crypto_fallback_register() 只分配结构体,不再注册代理 | 禁用态命名空间里无代理,新 transform 直连硬件 |
| 启用时 | fallback_enabled_store() → fallback_register_algs() 为每组分配 + 注册代理 | 代理只在用户明确启用时出现 |
| 禁用时 | 置 enabled=false + 阈值清零 + fallback_unregister_algs() | 新查找跳过代理;旧 transform 的代理被强制走硬件 |
| 释放时 | 代理 cra_destroy → fallback_alg_destroy() → 标记 dead + kref 释放 | 退役代保留到最后一个使用它的 transform 释放 |
| 框架释放 | fallback 本身 kref 计数,全部代理释放后才 crypto_fallback_free() | 避免结构体先于代理销毁的 UAF |
🔬 关键代码
diff 逐字来自 lore 归档的 [PATCH 4/4] 原文。按补丁的三个核心逻辑点组织,每段讲"为什么这么写"。
@@ -476,28 +482,39 @@ static ssize_t fallback_enabled_store(struct device *dev,
for (i = 0; i < fallback->num_groups; i++)
WRITE_ONCE(fallback->groups[i].threshold.value,
fallback->groups[i].value);
+
+ if (!fallback->enabled) {
+ err = fallback_register_algs(fallback);
+ if (err) {
+ for (i = 0; i < fallback->num_groups; i++)
+ WRITE_ONCE(fallback->groups[i].threshold.value,
+ 0);
+ goto out_unlock;
+ }
+ }
} else {
+ WRITE_ONCE(fallback->enabled, false);
for (i = 0; i < fallback->num_groups; i++)
WRITE_ONCE(fallback->groups[i].threshold.value, 0);
+ fallback_unregister_algs(fallback);
}
WRITE_ONCE(fallback->enabled, enabled);
out_unlock:
mutex_unlock(&fallback->lock);
- return count;
+ return err ?: count;
}▲ 为什么这么写:启用分支里 if (!fallback->enabled) 是关键——防止重复 enable 时重复注册同代代理;注册失败则把阈值清零并走 out_unlock(不置 enabled,保持禁用态)。禁用分支先置 enabled=false 再注销,配合 fallback_register_algs() 里对已注册状态的判断,让"禁用与注册/注销"在并发视角下自洽。返回值从恒 count 改为 err ?: count,使注册失败能如实反映到 sysfs 写回。
+static void fallback_alg_destroy(struct fallback_alg *alg)
+{
+ struct crypto_fallback *fallback = alg->fallback;
+ unsigned long flags;
+ bool release = false;
+
+ spin_lock_irqsave(&fallback->alg_lock, flags);
+ alg->dead = true;
+ alg->registered = false;
+ if (alg->registration_complete) {
+ list_del_init(&alg->list);
+ release = true;
+ }
+ spin_unlock_irqrestore(&fallback->alg_lock, flags);
+
+ if (release)
+ kref_put(&alg->refcount, fallback_alg_release);
+}
+
+static void fallback_skcipher_destroy(struct crypto_alg *base)
+{
+ struct skcipher_alg *alg;
+
+ alg = container_of(base, struct skcipher_alg, base);
+ fallback_alg_destroy(container_of(alg, struct fallback_alg,
+ alg.skcipher));
+}▲ 为什么这么写:这个回调由 Crypto API 在算法引用计数归零时调用(crypto_destroy_alg() → cra_destroy)。三个 type(skcipher/aead/ahash)各有一个 fallback_*_destroy 转发到 fallback_alg_destroy()。关键在 registration_complete:若注册尚未完成(disable 与注册竞态)就收到 destroy,只标记 dead、不释放(等注册完成回调 fallback_alg_complete_registration() 处理);只有注册完成后才把对象从链表摘下并 drop 引用。锁内改状态、锁外做 kref_put,避免持自旋锁调用可能睡眠的释放路径。
+ alg = kzalloc_obj(*alg, GFP_KERNEL);
+ if (!alg) {
+ err = -ENOMEM;
+ goto err_unregister_algs;
+ }
+
+ INIT_LIST_HEAD(&alg->list);
+ kref_init(&alg->refcount);
+ alg->fallback = fallback;
+ alg->threshold = &fallback->groups[i].threshold;
+ alg->type = group->benchmark.type;
+ if (strscpy(alg->driver_name, group->algs[j],
+ sizeof(alg->driver_name)) < 0) {
+ kfree(alg);
+ err = -ENAMETOOLONG;
+ goto err_unregister_algs;
+ }
+
+ kref_get(&fallback->refcount);
+ spin_lock_irqsave(&fallback->alg_lock, flags);
+ list_add_tail(&alg->list, &fallback->algs);
+ spin_unlock_irqrestore(&fallback->alg_lock, flags);
+
+ err = fallback_register_alg(alg, fallback->owner);
+ err = fallback_alg_complete_registration(alg, err);▲ 为什么这么写:每次启用周期每个代理都是新分配的(diff 里新注释:"Allocate proxy algorithms per enable cycle because their destruction can be deferred until transforms allocated before disable release them")。kref_get(&fallback->refcount) 让代理持有框架结构体引用,保证框架不会在代理还活着时被释放;list_add_tail 进链表后由 fallback_register_alg() 真正注册,再用 fallback_alg_complete_registration() 标记注册完成。注销方 fallback_unregister_algs() 只挑 registered 的项注销并 drop 引用,未完成注册的项留给 destroy/complete 回调处理。
📈 性能影响
补丁 4/4 本身未单独提供基准数据(commit message 无数字)。下表是系列 cover letter 报告的框架级数据,作者自报,未独立验证。它同时展示了本补丁所针对的"代理开销"的量级:同一台机器上,禁用态直连硬件(Disabled 列,本补丁生效后的默认形态)与启用态走代理(Enabled 列)之差,就是代理层在请求路径上的代价。
| 场景/用例 | 运行环境 | 直连硬件 (Disabled) | 走代理 (Enabled) | 差异 |
|---|---|---|---|---|
| tcrypt AES-128-CBC,16B | OpenWrt 社区镜像 / Linux 6.18 / W1700K2 HW2.1 板 / Airoha AN7581 E2 SoC;中位数周期,5 次运行 | 322 周期 | 18 周期 | -94.4% |
| tcrypt AES-128-CBC,64B | 274 | 43 | -84.3% | |
| tcrypt AES-128-CBC,128B | 292 | 74 | -74.7% | |
| tcrypt AES-128-CBC,256B | 312 | 138 | -55.8% | |
| tcrypt AES-128-CBC,1024B | 434 | 439 | +1.2% | |
| tcrypt AES-128-CBC,1424B | 492 | 488 | -0.8% | |
| tcrypt AES-128-CBC,4096B | 980 | 1023 | +4.4% |
| 场景/用例 | 运行环境 | 直连硬件 (Disabled) | 走代理 (Enabled) | 吞吐增益 |
|---|---|---|---|---|
| OpenSSL 3.5.7 AF_ALG AES-128-CBC,16B | 同上环境;两次各 2 秒运行取均值 | 0.560 MB/s | 1.257 MB/s | +124.5% |
| OpenSSL,64B | 2.430 | 4.639 | +90.9% | |
| OpenSSL,256B | 9.106 | 14.168 | +55.6% | |
| OpenSSL,1024B | 30.279 | 30.332 | +0.2% | |
| OpenSSL,8192B | 88.807 | 89.383 | +0.6% | |
| OpenSSL,16384B | 101.220 | 102.068 | +0.8% |
说明:所有数字来自系列 cover letter(lore 原文),标注"作者自报,未独立验证"。补丁 4/4 的收益是逻辑推导:它把"禁用=直连硬件"落实为默认态,因此上表 Disabled 列即本补丁生效后的默认形态;Enabled 列(走代理)中 1024B 以上大请求多出的 3~4% 周期,正是本补丁在禁用态消除的那层开销。阈值基准:启用耗时 0.82s,为每组选一个代表硬件/软件对,在 16/64/128/256/512/1024/1420/2048/4096/8192/16384B 共 11 档实测选交叉点;选定阈值如 raw_aes=512B、hash_sha2=-1(软件全程更快)。
🔄 方案演进
本补丁为系列首版(v1),截至 2026-08-03 的 lore 归档,系列 4 篇均无公开回复。演进以下列真实信息为准:
⚠️ 风险与局限
enabled,退役代会堆积,占用内存直到那些 transform 释放。代码用 kref 保证正确性,但"多久才回收"取决于 transform 生命周期。(解读 AI 分析)
② 引用计数竞态(CRITICAL 潜在):新增的
dead / registration_complete / registered 三态 + alg_lock 自旋锁,处理"禁用与注册完成并发"这类窗口。若 fallback_alg_complete_registration() 与 fallback_alg_destroy() 的引用平衡任一分支出错,要么泄漏(退役代永不释放),要么 UAF(transform 仍引用已释放算法)。这是本补丁最需要维护者逐行 review 的点。(解读 AI 分析)
③ 全局注册锁的 toggle 成本(MINOR):注册/注销走 Crypto API 的
crypto_alg_sem 写锁,toggle enabled 期间会影响并发算法查找/分配。该操作低频(仅管理员开关时),一般可接受,但高并发 crypto 负载下开关瞬间可能有短暂分配延迟。(解读 AI 分析)
④ 行为变化:
fallback_enabled_store() 返回值从恒 count 改为 err ?: count;benchmark 失败时每组 err 显式归零(此前错误被隐式吞掉)。sysfs 写回在注册失败时会报告错误,语义更清晰,但对依赖旧行为的脚本可能产生新的失败可见性。(解读 AI 分析)
🔗 交叉引用
PATCH 2/4:crypto: introduce dynamic software fallback — 引入
crypto/fallback.c 框架本体(本补丁优化的对象)crypto: qce - Remove driver(Eric Biggers) — 系列动机引用的 QCE 讨论,提议移除低效加速器
QCE 讨论 v1(2026-07-12) — cover letter 脚注 [1]
QCE 讨论 v2(2026-07-12) — cover letter 脚注 [2]
stevenj/ph-openwrt-mtk-eip93 — 社区对 EIP93 的静态 AES size override(本框架的替代方案)
✅ 关键洞察
- 发现:补丁解决"特性默认关闭却仍常驻代理、热路径白付一层派发开销"的框架缺陷——把代理注册/注销绑定到 per-device 的
enabled开关,禁用态新 transform 直连硬件、启用态才注册代理,并用 kref +cra_destroy保留退役代直到旧 transform 释放。 - 证据:补丁未单独量化;系列数据(作者自报,未独立验证)显示代理层在 4096B 大请求上较直连约 +4.4% 周期(980→1023),这正是本补丁在禁用态消除的量级;启用后小请求经软件路径 16B 周期 -94.4%、OpenSSL 吞吐 +124.5%。
- 边界:收益仅作用于"禁用态"(默认形态)——启用态代理刻意存在(那是框架的目的);新建 transform 受益,已绑定代理的旧 transform 保留到释放;本补丁为框架系列一部分,单独合入无意义。
- 风险 / 建议:最需 review 的是引用计数竞态(
dead/registration_complete三态与alg_lock);建议维护者补充"长寿命 transform 存在时反复 toggle enabled"的压力测试,并确认退役代内存回收时序;系列尚未获 Crypto 维护者公开回复,等待 v2 讨论。
本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。