BPF OOM 内核补丁深度解析:用 eBPF 自定义 OOM 策略
前几篇文章展示了如何使用 eBPF 追踪 OOM 事件。但这还不够——我们只是在"看",不能"管"。内核的 OOM Killer 选谁杀谁,由 oom_badness() 算法决定,用户无法干预。
2025 年,Google 工程师 Roman Gushchin 提出了一套 BPF OOM 内核补丁,目标是让 eBPF 程序可以完全接管 OOM 处理策略。这是 Linux 内存管理子系统近二十年来在 OOM 领域最大的变化。
时效性说明:截至 2026 年 6 月,BPF OOM 补丁仍处于 RFC/审查阶段,尚未合入任何 Linux 内核主线版本。下面介绍的内容基于补丁集的技术方案——部分接口和设计可能在最终合入时发生变化。
传统 OOM Killer 的问题
Linux 的 OOM Killer 自 2001 年左右引入内核,核心逻辑基本没有大改过:
- 内存耗尽 → 触发
__alloc_pages_slowpath失败 - 调用
out_of_memory()→ 遍历所有进程 - 用
oom_badness()计算每个进程的"坏值"(基于 rss、swap、oom_score_adj) - 选"最坏"的进程杀死
问题在于这个"最坏"的判断是通用的,无法针对不同 workload 做优化:
- 数据库节点:希望优先保护数据库进程,杀掉占用缓冲池的查询进程
- AI 训练集群:希望优雅 checkpoint 而不是直接 kill -9
- Kubernetes 节点:希望感知 cgroup 层级,优先杀超限的容器而不是 kubelet
- 延迟敏感服务:希望在 OOM 之前就通过 PSI 信号做预防性处理
这些需求用传统方式实现非常困难——要么改内核代码重新编译,要么在用户态写 OOM daemon(如 oomd、systemd-oomd),但两者都有显著的维护成本和局限性。
BPF OOM 补丁系列概览
Roman Gushchin 的方案不是简单的"在现有框架上加几个 hook",而是重新设计了 OOM 处理流程:
flowchart TD
classDef new fill:#FFECB3,stroke:#E65100,color:#BF360C
classDef existing fill:#E3F2FD,stroke:#1565C0,color:#1565C0
classDef result fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
ps@{ shape: rounded, label: "PSI memory pressure\\(early trigger\\)" }
alloc@{ shape: rounded, label: "`__alloc_pages` fails\\(direct reclaim\\)" }
hook@{ shape: diamond, label: "BPF OOM hook (new hook point)" }
bpf@{ shape: proc, label: "BPF 程序\n\\(自定义策略\\)" }
default@{ shape: proc, label: "Default OOM Killer\n\\(oom_badness\\)" }
kill@{ shape: stadium, label: "`bpf_oom_kill_process()`\\(用户自定义\\)" }
ck@{ shape: stadium, label: "系统恢复" }
ps --> hook
alloc --> hook
hook -->|"BPF 附接"| bpf
hook -->|"无 BPF 处理"| default
bpf -->|"策略决策"| kill
kill --> ck
default --> ck
class ps,alloc existing
class hook,bpf,kill new
class default existing
class ck result关键设计思路:
- 内核插一个通用 hook 点:新增的 BPF OOM hook 在所有 OOM 路径上被调用
- 如果 BPF 程序附接了:完全交给 BPF 程序做决策
- 如果 BPF 程序没有附接或卸载了:回退到传统的
oom_badness()逻辑 - PSI 驱动的主动触发:不需要等到内存彻底耗尽,PSI 压力超过阈值即可触发 BPF 程序
这个设计保证了安全性——BPF 程序出问题也不会让系统死锁。
补丁发展时间线
| 版本 | 日期 | 补丁数 | 说明 |
|---|---|---|---|
| RFC v1 | 2025-04-28 | 12 | 初始提案,LWN 报道(lwn.net/Articles/1019230) |
| v2 | 2025-10-27 | 23 | 大幅修订,增加 memcg kfuncs 和 selftests |
| v3 | 2026-01-26 | 17 | 进一步调整函数接口,基于 review 反馈改进 |
- LWN 分析:Custom out-of-memory killers in BPF
- Patchset 概览:mm: BPF OOM
- Phoronix 报道(v3):Updated Linux Patches For Managing Out-Of-Memory Behavior Via BPF
- Kernel Recipes 2025 演讲:BPFOOM: using eBPF to customize the OOM handling(Roman Gushchin 主讲)
kfunc 接口详解
补丁集引入了多个新的 kfunc(BPF 可调用内核函数),让 eBPF 程序能和内核内存管理子系统交互:
bpf_oom_kill_process()
| |
在 BPF OOM 程序中调用,以与内核 OOM Killer 完全相同的方式杀死指定的进程。oc 是当前的 OOM 上下文,p 是选中的目标进程。这个 kfunc 被声明为 sleepable,因为在杀死进程的过程中可能需要等待锁或 I/O。
bpf_out_of_memory()
| |
关于 attachment 机制
如何让 BPF 程序与内核 OOM hook 绑定,是补丁集仍在讨论的设计问题。RFC v1 采用了 fmodret(function modifier return)方式——内核调用 OOM hook 时先执行 BPF 程序,BPF 程序可以决定是否接管处理。社区也在讨论是否有更好的方案(如引入新的 BPF 程序类型)。下面是一个概念性示例,展示自定义 OOM 策略的编程模型。
一个概念性的 BPF OOM 策略示例
以下代码是概念性的伪代码,展示 BPF OOM 策略的编程思路。实际 kfunc 名称和接口以最终合入版本为准。
| |
PSI 驱动的主动 OOM
补丁集的第二部分是 PSI(Pressure Stall Information)驱动的 OOM 触发机制。传统 OOM Killer 是被动的——系统已经死锁了才出手。PSI 驱动的方案可以在内存压力达到某个阈值时就主动触发 BPF 程序做预防性处理:
| |
与用户态 OOM daemon 的对比
| 维度 | 用户态 OOM daemon(oomd / systemd-oomd) | BPF OOM |
|---|---|---|
| 响应延迟 | 秒级(PSI 通知 → 用户态处理 → kill) | 毫秒级(内核态直接处理) |
| 可靠性 | 内存压力下用户态进程可能被 OOM kill | 内核态执行,不受 OOM 影响 |
| 部署复杂度 | 独立的 daemon,需要配置和维护 | 加载 BPF 程序即可 |
| 数据可访问性 | 只能通过 /proc 获取有限信息 | 可直接访问内核数据结构 |
| 策略灵活性 | 完全灵活(用户态任意逻辑) | BPF verifier 限制(有限循环、有限栈) |
| 可观测性 | 日志 + 指标 | 日志 + 指标 + 完整内核上下文 |
两类方案并非互斥。理想情况下,BPF OOM 处理大部分常用场景,用户态 daemon 处理复杂策略。
当前状态与未来展望
截至 2026 年 6 月的状态:
- RFC v1(2025-04)→ v2(2025-10)→ v3(2026-01)持续迭代中
- 补丁仍处于 bpf-next 分支中,社区 review 阶段
- Roman Gushchin 在 Kernel Recipes 2025 上做了专题演讲
- 尚无明确的合入时间表
这套补丁面临的核心挑战:
- 安全性:允许 BPF 程序杀进程是高风险操作,verifier 需要确保程序行为可预测
- 锁依赖:BPF 程序可能持有 oom_lock 时调用可能触发 I/O 的操作
- 分层策略:cgroup 层级中的 OOM 策略继承关系如何处理
- 回退机制:BPF 程序出问题时如何确保系统不会死锁
小结
BPF OOM 是 Linux 内存管理领域近年来最有意义的变化之一。它解决了 OOM Killer 二十多年来"一刀切"的痛点,让用户可以根据自己的 workload 定制 OOM 策略。虽然补丁还未合入主线,但其设计思路和技术方案已经足够有启发意义。
这个系列到这里就结束了。从 eBPF 基础概念开始,到 OOM 事件追踪的实现,再到容器级定位和内存分配分析,最后到 BPF OOM 内核补丁——希望能为你提供一个从"看"到"管"的完整 eBPF 内存可观测性学习路径。