【内核前沿】BPF 革命:跨越“睡眠”与“原子”的鸿沟,KF_FORBID_FAULT 补丁详解

张开发
2026/5/17 9:30:49 15 分钟阅读
【内核前沿】BPF 革命:跨越“睡眠”与“原子”的鸿沟,KF_FORBID_FAULT 补丁详解
1. 引言BPF 的“身份难题”在 Linux 内核的世界里上下文Context决定了你能做什么。长期以来BPF 程序被严格划分为两类普通 BPF 程序运行在原子上下文Atomic Context速度极快但不允许睡眠无法执行耗时操作或处理页错误。可睡眠 BPF 程序Sleepable BPF2020 年引入允许调用bpf_copy_from_user等可能触发页操作的函数。痛点在于目前这两者是“水火不容”的。一旦一个程序被标记为 Sleepable它就很难在执行过程中安全地获取那些只能在原子上下文中持有的内核锁。这导致开发者陷入两难要么为了性能放弃用户态数据拷贝要么为了功能放弃内核关键资源的锁定。2. Mohan 的创新从“全局追踪”到“指令级追踪”开发者Puranjay Mohan近期提交了一套颇具野心的补丁集。其核心思想是打破“全程序睡眠”的死板标记让 BPF 验证器Verifier具备动态追踪能力。2.1 技术原理KF_FORBID_FAULT 标志位目前的 BPF 验证器通过KF_ACQUIRE和KF_RELEASE来管理资源的生命周期。Mohan 在此基础上引入了一个新的标记KF_FORBID_FAULT。工作流1. 当一个可睡眠 BPF 程序调用带有KF_FORBID_FAULT的KF_ACQUIRE函数如获取某种锁时验证器会将当前指令流标记为“禁止睡眠”。2. 在此期间程序进入临时原子状态。3. 直到调用对应的KF_RELEASE验证器才重新允许程序执行睡眠操作。3. 实战案例重塑 task_vma 迭代器Mohan 以task_vma迭代器为例展示了该机制的威力。在旧机制下遍历任务的虚拟内存区域VMA非常痛苦。因为 VMA 结构体必须在持有mmap_lock时才有效而持有该锁会自动禁止页错误以防死锁。这意味着你虽然拿到了 VMA 数据却没法把它们拷贝到用户态。新补丁下的代码模式bpf_for_each(task_vma, vma, task, 0) { u64 start vma-vm_start; /* * [原子上下文] * 此时持有 mmap_lock禁止页错误 * 但可以安全访问 vma 结构体指针。 */ bpf_iter_task_vma_release(___it); /* * [切换回可睡眠上下文] * mmap_lock 已释放VMA 指针虽然失效 * 但我们可以利用刚才拿到的 start 地址 * 安全地进行用户态拷贝。 */ bpf_copy_from_user(buf, sizeof(buf), (void *)start); }4. 社区交锋Starovoitov 的“一票否决”尽管愿景美好但 BPF 维护者Alexei Starovoitov对目前的实现并不买账。4.1 命名与语义的争议Starovoitov 认为KF_FORBID_FAULT的名字不够直观。他建议将资源获取进一步细分为KF_ACQUIRE用于普通的引用计数。KF_ACQUIRE_LOCK专门用于实际的锁操作并天然集成“禁止睡眠”的语义。4.2 验证器架构的洁癖最致命的反对意见在于 Mohan 重新利用了验证器中用于追踪堆栈槽位Stack Slots迭代器的id字段。Starovoitov 明确表示“不行这走不通。我们必须推倒重来。”他担心这种针对特定迭代器的“打补丁”行为会破坏验证器的通用性尤其是在Amery Hung正在努力清理验证器 ID 管理机制的当下。5. 展望分步走的策略面对阻力Mohan 并没有停下脚步而是采取了“曲线救国”的方案短期方案他提交了另一套补丁将task_vma迭代器修改为使用per-VMA 锁。这样迭代器可以在交出 VMA 副本后立即释放锁从而在现有框架下直接支持 Sleepable 上下文。长期方案借鉴Eduard Zingerman的建议Mohan 计划在未来对内核锁类型中断锁、RCU 锁、抢占锁等进行更深度的重构使验证器的逻辑更加模块化和通用。总结BPF 向“通用编程语言”进化的道路注定充满坎坷。Mohan 的补丁虽然在实现细节上遭遇了挑战但它为我们揭示了一个更加灵活、更接近原生内核编程的 BPF 未来。

更多文章