挂起与恢复 BPF 程序:协程如何改变 BPF 编程模型
本文地址:https://www.ebpf.top/post/suspending-resuming-bpf
BPF 程序可以用来扩展 Linux 内核的诸多方面,但有一个硬约束:程序必须在开始执行时的同一上下文中一口气跑完。Kumar Kartikeya Dwivedi 想打破这条规则,让 BPF 程序可以写成协程(coroutine)。他在 2026 年 Linux 存储、文件系统、内存管理与 BPF 峰会(LSFMM+BPF)上介绍了这项仍在实验中的工作。若设计落地,那些跨上下文、耗时的 BPF 任务会好写得多。
Dwivedi 指出,很多逻辑任务天然分散在不同代码路径、不同时刻:执行在多处跳转,计算会被挂起,模式并不稀奇。能在 BPF 里直接表达这种结构,部分内核扩展会轻松不少。拿栈追踪来说:内核已有机制,可合并采集内核态与用户态的调用栈;在切回用户态前一刻再采用户态栈帧,往往更高效。流程是:请求到达时先跑内核段,然后挂起,用户态段稍后再续上。要把 BPF 嵌进这条链路,目前只能把一件事拆成多个独立函数——因为正在跑的 BPF 程序没法中途挂起。
用户态网络也碰得到同类麻烦。sendmsg() 发包最终会调到 qdisc_run(),后者还可能替别的线程发送它们排队的包——主任务和后续工作又不在同一时间、同一地点完成。Dwivedi 提到,有人试过把整应用写进 BPF,问题只会更多。
传统上,BPF 靠 hook 和回调拼语义,这没问题,也够内核开发者用。代价是:挂起/恢复逻辑得自己写,程序得拆成好几段。Dwivedi 的方案是引入协程——能挂起、把控制权还给内核、之后再恢复的函数。会上他接着讲了内核侧的设计草图。
他倾向无栈协程(stackless coroutine),思路接近 Rust 的 async 或 C++20 协程:编译器把顺序代码改写成可挂起、可恢复的形态。对 BPF 验证器来说,改动可以很小——恢复协程看起来就像一次普通的间接调用,中间状态的保存/加载交给编译器。验证器仍要证明整体控制流合法,并可能要适配「代码被拆到多个内核上下文」的情形。
为说明验证器将看到的接口,Dwivedi 简述了 C++ 协程的实现:编译器生成一个结构体,前两个字段固定是 resume() 和 destroy() 两个函数指针;结构体里还有一个挂起点索引,resume() 用它配合 switch 跳到该续跑的代码。跨挂起点要保留的变量放进结构体;只在本段有效的变量留在栈上,挂起时随栈一起丢掉——但锁、引用计数对象不能「假装忘掉」,验证器会像检查普通返回路径一样盯住它们。函数指针排在结构体最前面,通用运行时代码不必知道协程帧具体多大、怎么排布。
Rust 异步函数编译结果与此类似,只是用类型系统携带 resume() / destroy();布局可以对齐到与 C++ 相同,验证器大概率只认这一种帧格式。在类型跟踪层面,协程帧可以当「第二块栈」:寄存器值 spill 进去、再 load 出来,不必单独造一套类型规则。
在通用 BPF 规则之外,验证器还要额外保证:两个函数指针不能被改写;索引只能落在合法挂起点;挂起状态下随时允许 destroy()(程序可能被卸载)。挂起点不能持锁——可复用「返回前必须放锁」的既有逻辑;某些 map 值若跨挂起点持有,也可能需要被标记失效。
Andrii Nakryiko 追问:若恶意把索引拨回更早的位置,会不会造出验证器看不见的循环?Dwivedi 回应,协程体照常过验证,每个挂起点对 resume() 的调用也会被检查,循环检测与今天类似。更隐蔽的「环」呢?他举例:两个 BPF 程序互相为对方的定时器设闹钟,早就做得到——只要不让内核整体停摆,这类无限循环未必是安全问题。再说,验证器把程序压在百万条已验证指令以内,也拦不住有人给每个 hook 挂一条 999,999 指令、大量调用昂贵 kfunc 的程序把机器拖死。上限防的是可能死锁的无限循环,保证系统还能往前推进,不是帮内核省 CPU。
只要从任一挂起状态都能合法 destroy()(卸载场景)并 resume(),BPF 的安全边界就不会被协程绕开。就算有人构造了花哨的无限循环,每次挂起仍须放锁、交还控制权,内核不应因此被死锁。
原型已在路上,离进主线还有距离:协程程序的 BTF 要扩展;测试用的 C++ 样例还依赖「函数可返回聚合类型」,验证器也得跟;Rust 支持相对好加。更远的一个实验方向,是让挂起的计算在用户态与内核态之间来回切——学生做过雏形,远未可用;真做成了,应用可以先在用户态初始化,再进内核用原生 BPF,必要时再退回用户态,用户态与内核的界线会变得模糊——但那是后话。
近期 Dwivedi 打算把现有实现打磨到可上游化的程度。协程本身不会凭空造出新能力,目标是让 BPF 更容易嵌进那些没法简化成「一个 hook 搞定」的内核路径。
原文:Suspending and resuming BPF programs(LWN.net,Daroc Alden,CC BY-SA 4.0)
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/suspending-resuming-bpf/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2026-07-04 13:02:15.273496967 +0800 CST