想象你是城市的交通管理局工程师。城市的主干道就像操作系统的内核,每辆车都是一块被分配的内存。过去,你要查“哪辆车只进不出”(内存泄漏),必须把路面挖开,埋设地感线圈——那相当于修改内核源码,风险极大,一不留神整个城市的交通都会瘫痪。现在,eBPF 出现了。它像一套“智能交通摄像头”,不挖路、不封道,只需在路旁架起可编程的观察哨所,就能实时记录每一辆车的进出,精准找出那些只占用车位却从不离开的“僵尸车”。这套系统不仅看得见,还能分析得明明白白,甚至能在爆满(OOM)之前提前预警。从交通指挥的角度,你将理解 eBPF 如何工作、它手中的数据台账(Map)、安置在路口的各类摄像头(kprobe/uprobe),以及 BCC 与 bpftrace 这两套指挥平台,最终掌握在真实场景中定位内存泄漏的完整方法论。
掌握 eBPF 内存监控,你将拥有在生产环境“不动刀”地透视内核内存行为的能力,从被动背锅变成主动定位,这是内核开发、SRE 运维与性能调优从业者的核心竞争力所在。
可视化演示
动态演示eBPF追踪内存监控实战的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
eBPF追踪内存监控实战:事件驱动 事件驱动架构的核心原理与事件定 观察者模式的同步事件通知机制 核心机制可视化 eBPF追踪内存监控实战整体架构与关键组件
eBPF沙箱原理
城市交通管理局过去想观察车流,只能选择挖开路面埋地感线圈,相当于修改内核源码——风险高、周期长,还可能引发交通事故。现在,他们在路旁架起了一套“智能观察哨所”:外形小巧、无需破土动工,却能精准捕捉每一辆经过的车。这套哨所就是 eBPF。它是嵌入在 Linux 内核中的可编程沙箱,专为安全观测而设计。你只需要编写一段小代码,加载进内核,它就会在指定事件发生时自动执行,全程不碰内核源码一根汗毛。
为什么敢让第三方代码跑在内核里?因为 eBPF 有一道严格的“安检门”——验证器机制。在程序加载阶段,验证器会从头到尾检查这段代码:是否存在死循环、是否越界访问内存、是否调用了非法函数。任何有安全隐患的程序都会被当场拦住,内核绝不会让一颗“定时炸弹”进入交通指挥中心。
通过验证的 eBPF 程序采用事件驱动模式运行:平时它安静地挂在指定钩子上不消耗资源,只有当目标事件(如系统调用、内核函数调用、网络包到达)发生时,它才被唤醒执行。这种设计让 eBPF 的观测开销极低,如同摄像头待机时不耗电,有人经过才点亮。
交通管理局终于拥有了不挖路就能看车流的利器:eBPF 沙箱让观测变得安全而轻盈,从此“透视内核”不再需要伤筋动骨。
- 总运行开销系统的整体 CPU/内存消耗,是评价监控工具的重要指标。
- 系统基础开销未安装任何观测工具时,系统正常运行本身的消耗。
- 观测附加开销eBPF 探针被触发后执行处理逻辑所额外增加的消耗。
- 综合结论eBPF 的设计目标就是让观测附加项趋向于零。
Map与辅助函数
摄像头拍到车辆信息后,数据不能直接丢在马路上——你需要一个记录本,把每辆车的进出情况记下来。eBPF 里这个“记录本”叫做 Map 数据结构。它是内核态与用户态共享的一块键值存储区域,就像交警手上那本“车流台账”。eBPF 程序在内核态把数据写入台账,用户态的监控程序则从台账中读取统计数据。Map 有哈希表、数组、LRU 等多种形态,应付不同场景:记录进程分配总量用 hash,记录固定长度的统计信息用 array。
但是,在内核里写台账不能随便动手,必须使用官方发放的专用工具——helper 函数。这些函数是内核预先定义好的安全接口,eBPF 程序只能调用这一小撮“持证工具”。比如 map_update_elem() 是“盖章工具”,map_lookup_elem() 是“翻阅工具”。验证器会确保 eBPF 程序不会绕过这些工具去直接操作任意内核内存,从根本上杜绝了“乱写乱画”的可能。
正是这种“台账本 + 专用工具”的组合,打通了内核与用户态之间的数据共享通道。内核负责探测、记录,用户态负责整理、展示,各司其职,数据流转井井有条。Map 还会自动处理并发访问,多个 CPU 同时更新数据也不会打架。
Map 和 helper 让交通指挥中心有了可靠的数据基础:摄像头负责捕捉,台账本负责记录,工具包负责安全。数据有了“家”,监控才有底气。
- 负载因子符号它标识了数据结构的拥挤程度,数值越高越危险。
- 条目数量当前台账中已经登记了多少条记录。
- 容量上限台账本最多能够容纳的总记录条数。
- 比率关系不要让你的监控数据占据超过容量的 70%,否则就要扩容。
kprobe探针
回到交通管理现场:你想观察车辆进入高速是否拥堵,最直接的办法是在高速匝道口安装摄像头——每当一辆车驶入匝道,摄像头就自动拍下车牌与货物量。对应到内核世界,这就是 kprobe 探针。kprobe 是 eBPF 家族中负责内核动态追踪的装置,它把你关心的内核函数(比如 kmalloc 内存分配函数)设为一个“观察点”,当系统中任何进程调用该函数时,探针立即触发。
kprobe 的实现原理很巧妙,它并不修改内核源码,而是在运行时把目标函数入口处的第一条指令临时替换为断点指令。这就像在匝道上放一个“感应器”,车压过感应带时触发拍照。这种函数入口插桩方式不需要重新编译内核,甚至可以针对正在运行的生产系统动态挂载,真正做到“装摄像头不用封路”。
在内核内存监控中,kprobe 最典型的应用就是分配路径观测。当进程执行 kmalloc 请求分配一块内存时,kprobe 探针会被触发,此刻 eBPF 程序可以从寄存器中读取本次分配的大小、进程 PID、用户态调用栈等信息,并把这些数据写入 Map 台账。每一次“车辆入场”都被完整登记在案。
kprobe 把高速匝道上的每一辆“内存车”都拍了下来,但它在内核主干道上工作。如果你把目光转向用户态程序,还需要另一套摄像头。
- 分配速率它能够反映系统内存分配活动的频繁程度。
- 分配事件总数kprobe 探针在窗口内捕获到的所有分配事件数量。
- 观察时间窗这次观测所持续的时间跨度,窗口越长数据越稳定。
- 速率公式速率高意味着系统可能在频繁申请内存,需要重点关注。
uprobe探针
当你想知道某个小区住户的生活轨迹,最佳方式不是观察整条城市主干道,而是在小区门口装一套摄像头。与此对应,uprobe 探针专注于用户态追踪——它不盯内核函数,而是直接扎进应用程序的代码里。在 eBPF 监控体系中,kprobe 管内核要道,uprobe 管应用门口,两者配合才能覆盖从用户态到内核态的完整内存链路。
uprobe 的动态能力来自对程序二进制文件的符号解析。当加载一个可执行文件时,eBPF 会根据函数名(如 malloc)在 ELF 符号表中查找到对应地址,然后把该地址处的指令替换为触发探针的跳转指令。这让它可以观测应用行为的方方面面:应用调用了多少次 malloc、每次申请多少字节、调用来自哪个函数。对内存监控来说,uprobe 最亮眼的价值是追踪用户态分配——无论是 malloc 还是 free,每一次调用都无所遁形。
与 kprobe 侧重内核视角不同,uprobe 让你站在“应用门口”思考问题。比如一个 Java 服务频繁申请大块内存,uprobe 可以告诉你这些申请发生在哪些用户态函数中,从而把嫌疑范围从“内核”缩小到“某一段业务代码”。
uprobe 的“门口摄像头”让用户态应用的内存行为变得透明。但装了摄像头只是第一步,真正的挑战在于如何设计一套完整的监控方案——从记录、配对到判定。
- 活跃对象数当前仍然存活在应用程序中的内存对象数量。
- 累计分配次数uprobe 在追踪周期内捕获到的所有 malloc 调用总次数。
- 累计释放次数所有 free 调用成功释放的对象数量。
- 泄漏判据活跃数持续上涨说明释放不彻底,需要借助堆栈定位泄漏点。
内存监控方案
有了摄像头(kprobe/uprobe)和台账本(Map),我们终于可以开启一套正式的“停车管理方案”:车辆进场要登记,出场要核销,定期盘点滞留车辆。这套方案在内存监控中的专业说法是分配统计。当 kmalloc 或 malloc 被调用时,eBPF 程序记录下指针地址、分配大小、进程 PID 和调用栈,并存入 Map。从此每一次内存分配都有了“入场登记”。
第二步是释放配对。当 kfree 或 free 被调用时,eBPF 程序取出指针,在 Map 中查找对应的分配记录,然后将其删除。这相当于“出场核销”——车辆离开停车场,登记册上就划掉这一条。整个查找过程依托 Map 的哈希索引,可以在亚微秒级别完成,不会对应用性能造成明显干扰。
有了进场登记和出场核销,“漏网之鱼”就无处遁形:泄漏判定的核心逻辑就是映射扫描。监控程序每隔一段时间遍历 Map,凡是长期残留的记录,都是“只进不出”的嫌疑车辆。再配合调用栈信息,你可以精确知道是哪一行代码分配了这些内存。若某个进程的残留记录数持续上涨,内存泄漏便已实锤。
这套“登记—核销—盘点”的监控方案,让内存泄漏从“凭空猜测”变成了“有据可查”。接下来需要工具把这些数据变成看得见的结果。
- 泄漏总量它是衡量内存泄漏严重程度的最直接指标。
- 单次分配大小第 i 次分配请求所申请的字节数。
- 单次释放大小第 j 次释放操作所归还的字节数。
- 累计求和关系分配总和减去释放总和得到的正值,就是还没归还的部分。
BCC工具实战
执法记录仪收集了海量数据,但交通管理局需要一个“数据大屏”把统计信息可视化。BCC 就是 eBPF 生态中那套功能完备的指挥中心平台。它的全称是 BPF Compiler Collection,最大特色是提供了一整套 Python 接口——你可以在 Python 脚本中直接书写并加载 eBPF 程序,而无需单独编写 C 代码并手动编译。这好比用一台“预制机柜”连接了所有摄像头:插上电源(加载内核代码),数据就自动流到你的终端。
BCC 的工作方式非常符合工程直觉。在 Python 脚本里,你用字符串嵌入一段 C 语言编写的 eBPF 程序,BCC 会自动帮你完成编译、验证和加载。以内存追踪为例,脚本示例通常包含这几步:定义 BPF 程序文本、调用 attach_kprobe 将探针挂到 kmalloc 上、然后通过 perf_buffer_poll 或直接读取 Map 来获取数据。整个过程不需要写内核模块、不需要重启系统,几十行代码就能完成一次完整的内存探察。
当数据量较大时,直方图是最直观的呈现方式。直方图输出把内存块的大小分布按区间统计,比如“1-4KB 分配了 1000 次、4-16KB 分配了 200 次”,一眼就能看出系统的分配倾向。BCC 还支持对 Map 内容排序,轻松找出分配总量最高的 Top 进程。
BCC 这套“指挥大屏”适合深度分析,但当你凌晨三点被电话叫醒,需要快速判断“谁在泄漏”,还有更轻便的选择。
- 直方图桶计数第 k 个大小区间内累积的样本数量。
- 指示函数当样本属于该区间时取 1,否则取 0,用于选择性累加。
- 全样本求和把所有样本逐一判定,最终得到该桶的总数。
bpftrace分析
深夜值班的运维工程师需要的是“手持执法仪”:一台便携设备,对着小区门口扫一下,立即显示进出的车流统计。bpftrace 就是 eBPF 生态中的那台便携执法仪。它采用单行命令的语法设计,让你用短短一行脚本就能完成采样、过滤、统计和输出。一改 BCC 需要编写完整 Python 工程的厚重感,bpftrace 把常用操作压缩成极简表达方式,上手门槛几乎为零。
虽然 bpftrace 小巧,但它的脚本语法五脏俱全。它以探针点作为命令行参数(比如 kprobe:kmalloc),配合大括号内的动作块(比如 { @[comm] = count(); })完成统计。@ 符号代表 Map 变量,count() 是内置的计数函数,kstack/ustack 可以获取内核或用户态调用栈。这套语法遵循“你要什么就写什么”的哲学,没有繁琐的工程样板。
在故障现场,bpftrace 最擅长现场分析。执行一条 bpftrace -e 'kprobe:kmalloc { @[kstack] = count(); }',运行十秒钟,它就能输出哪个内核调用栈触发了最多的内存分配。这个 Top 栈就是你的第一个破案线索。同样的思路也可以对 malloc/free 做差值统计,快速找到“分配了却不释放”的用户态函数。
bpftrace 让我们可以“三十秒巡一次街”。但无论工具多么顺手,真正的考验在于当 OOM 真的发生时,你是否能在那几分钟内找到真凶。
- 目标调用栈我们最终要锁定的那个嫌疑堆栈。
- 最大参数函数它能够找出使某个表达式取得最大值的自变量值。
- 堆栈计数每种调用栈在所有采样事件中出现的总次数。
- 排序取优逐栈比较计数,排名第一的栈就是监控的热点所在。
OOM排查实战
当一个城市的停车场彻底爆满,交管部门不得不启动“强制拖车”程序——内核里的 OOM Killer 会挑选一个占用内存最多的进程直接杀掉。虽然 OOM Killer 能暂时“缓解堵车”,但如果你不找到“只进不出”的车队,悲剧迟早重演。借助 eBPF,我们可以完成一次漂亮的事后根因定位。
首先,eBPF 的 Map 台账让增长曲线得以回放。记录每个进程在 OOM 前一段时间内的内存分配速率和总量,绘制出“内存增长曲线”,你会清楚看到哪个进程像“失控的仓库管理员”一样疯狂进货。通常 OOM 前 30 分钟是黄金排查窗口,通过历史记录就能锁定高增长的嫌疑进程。
其次,结合 kprobe/uprobe 记录的调用栈,定位到具体代码路径。比如某个 PID 的 malloc 事件集中在 login_service_alloc 函数中,基本可以断定是会话缓存忘记释放。这时你甚至可以把 eBPF 监控设为长期运行,配合阈值触发自动转储调用栈,形成监控告警体系:当某个进程的残留分配量超过阈值时,eBPF 自动把全量调用栈写入日志,相当于给系统装上了“事故黑匣子”。
eBPF 的“黑匣子”能力让 OOM 不再是一场盲目猜谜。从安装摄像头、建立台账、登记核销,到巡检与告警——这套完整的交通管理系统,终究能让你在城市的汹涌车流中抓住那只真正的“僵尸车”。
- 进程 OOM 分数它决定了进程被内核判处“死刑”的优先级。
- 进程常驻内存该进程实际占用物理内存的字节数。
- 系统总内存机器可用的全部物理内存容量。
- 计算得分占比越高的进程得分越高,被 OOM Killer 选中的概率越大。
AI 应用场景
SRE 运维:线上服务 OOM 根因排查
阶段:故障诊断与数据采集
解决的问题:传统排查 OOM 只能翻看系统日志和 dmesg,信息碎片化,难以回答“是谁在什么代码路径下不断分配内存”。eBPF 主动采集每个进程的分配/释放事件并记录调用栈,OOM 发生后可以利用历史监控数据还原现场。
具体案例:某电商平台凌晨促销出现频繁 OOM,SRE 通过 BCC 脚本追踪 malloc/free,发现某个订单模块的缓存对象在用户会话结束后未释放,残留对象以每分钟数十万个的速度累积。借助调用栈信息,开发团队十行代码修复,OOM 彻底消失。
业务可观测性:内核态内存热力分析
阶段:数据可视化与洞察
解决的问题:仅凭用户态指标无法看清“内存到底被谁吃掉”。eBPF 的 kprobe 可以在内核分配函数处实时采样,区分页缓存、Slab、进程堆等不同消耗者。
具体案例:一家云厂商在压测中发现某客户虚拟机内存回收频繁导致性能暴跌。通过 bpftrace 挂载 shrink_slab 和 __alloc_pages_nodemask,快速定位到内核文件缓存增长过快,最终通过调整脏页比例参数解决了问题。
数据处理:容器内存泄漏自动化巡检
阶段:异常检测与告警
解决的问题:Kubernetes 集群中容器数量大、内存申请释放频繁,人工巡检效率极低。eBPF 监控脚本定时扫描 Map 中的残留分配记录,将异常进程的调用栈自动上报告警平台。
具体案例:某大数据团队每天运行上百个 Spark 任务,偶发节点内存耗尽。他们部署了一个基于 BCC 的巡检服务,每五分钟扫描一次容器维度的内存分配残留,配合告警,将问题定位时间从小时级降低到分钟级。
Python 演示
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""用 eBPF (BCC) 追踪 kmalloc 与 kfree,输出进程维度的内存分配统计""" from bcc import BPFfrom time import sleep # eBPF C 程序:挂载到内核内存分配与释放路径bpf_text = """#include <uapi/linux/ptrace.h>#include <linux/sched.h> BPF_HASH(alloc_bytes, u64, u64);BPF_HASH(free_count, u64, u64); int trace_kmalloc(struct pt_regs *ctx, size_t size) { u64 pid = bpf_get_current_pid_tgid() >> 32; u64 key = pid; u64 *total = alloc_bytes.lookup(&key); u64 new_total = (total ? *total : 0) + size; alloc_bytes.update(&key, &new_total); return 0;} int trace_kfree(struct pt_regs *ctx) { u64 pid = bpf_get_current_pid_tgid() >> 32; u64 key = pid; u64 *cnt = free_count.lookup(&key); free_count.update(&key, (cnt ? *cnt : 0) + 1); return 0;}""" bpf = BPF(text=bpf_text)bpf.attach_kprobe(event="__kmalloc", fn_name="trace_kmalloc")bpf.attach_kprobe(event="kfree", fn_name="trace_kfree") print("采集 3 秒内的内核内存分配数据...")sleep(3) print("\n%-12s %-16s %-12s %s" % ("PID", "分配字节数", "释放次数", "平均每次大小"))rows = []for k, v in bpf["alloc_bytes"].items(): pid = k.value alloc = v.value cnt_val = bpf["free_count"].get(pid) fcnt = cnt_val.value if cnt_val else 0 avg = alloc / fcnt if fcnt else 0 rows.append((pid, alloc, fcnt, avg)) for pid, alloc, fcnt, avg in sorted(rows, key=lambda r: r[1], reverse=True)[:10]: print("%-12d %-16.2fKB %-12d %.2fKB" % (pid, alloc / 1024, fcnt, avg / 1024))
BPF_HASH(alloc_bytes, u64, u64)定义一个以进程 PID 为键、分配字节数为值的 Map,用来记录每个进程的累计分配量。bpf_get_current_pid_tgid() >> 32从 eBPF 上下文中获取当前进程的 PID,右移 32 位是为了去掉线程 ID 只保留进程 ID。bpf["alloc_bytes"].items()遍历用户态读取 Map 的全部键值对,将内核中累积的统计结果拉到用户空间进行分析。sorted(rows, key=lambda r: r[1], reverse=True)按分配总字节数降序排序,让分配量最高的进程排在最前面,方便快速锁定热点。bpf.attach_kprobe(event="__kmalloc", ...)将探针挂载到内核__kmalloc函数入口,探测内存分配事件。
常见误区
误区我一直以为要用 eBPF 就必须下载内核源码、编译安装新内核,否则内核不认这些探测代码。因为这个原因,我连试都不敢试。
正解eBPF 最大的魅力恰恰在于它是动态的。kprobe 和 uprobe 都是在运行时把已有函数的指令临时替换为断点,不需要重新编译内核。就像安装摄像头不需要把高速公路拆掉重建一样。只要内核开启了 CONFIG_BPF 相关选项(主流发行版默认开启),你就能直接使用 BCC 或 bpftrace 进行追踪。
为何常见:传统的内核模块开发确实需要匹配内核版本并编译,很多人把这条经验错误地迁移到了 eBPF 上。
误区既然 eBPF 这么强大,我是不是可以写一个复杂的循环来遍历所有进程内存?只要逻辑正确,内核应该允许我运行更长时间的代码。
正解验证器对 eBPF 程序有严格限制:循环必须有边界且每条路径都会终止,程序指令数不能超过 100 万条,栈空间也有限。超限的程序会在加载阶段被验证器拒绝,根本不会进入执行状态。这就像交通法规规定摄像头只能拍固定角度,不能自由转动去偷窥任何角落。
为何常见:很多人把 eBPF 和普通内核模块混为一谈,忽略了它“安全优先”的设计哲学。
误区我在网上看到别人都用 kprobe 追踪内存,所以只要用 kprobe 就能搞定所有问题。用户态程序是不是也会经过 kmalloc?那 kprobe 就够了呀。
正解内核态和用户态是两个不同的世界。kprobe 只能观测内核函数如 kmalloc,它看不到用户程序里的 malloc。如果应用通过 malloc 申请了内存,内核 kmalloc 未必会被调用(比如互斥锁、文件缓存也可能触发内核分配)。所以追踪用户态应用的内存行为,应该用 uprobe 挂在 malloc/free 上。两者配合才能覆盖全链路。
为何常见:新手往往从“内核”入手学习,容易忽略用户态探针的存在。
误区我打算把每次内存分配的调用栈都存到 Map 里,这样以后随时可以追溯。好像也没人提醒过我 Map 有大小限制。
正解Map 是内核中的有限资源,每个 Map 的条目数量在创建时就固定了(比如 10240 条)。如果存满后再插入新元素,默认行为是丢弃或覆盖旧数据,这会直接影响监控的准确性。因此实战中通常通过 max_entries 设置合理容量,并配合用户态定期清理旧记录,或者使用 LRU 类型的 Map 自动淘汰不活跃条目。
为何常见:用户态的字典/HashMap 通常支持动态扩容,大家天然认为 Map 也能无限增长。
面试题
Q1. eBPF 最核心的价值主张是什么?
- A.加速内核网络包处理性能
- B.不改内核源码即可安全观测← 正确答案
- C.替代所有传统内核模块
- D.自动修复内核安全漏洞
答案:B。eBPF 的立身之本是“可编程沙箱+安全观测”,让开发者在不动内核源码的前提下写代码探查系统。虽然它可以做网络加速(如 XDP),但那只是应用之一。C 过于绝对,D 完全不是 eBPF 的功能。
Q2. eBPF 验证器的主要职责是什么?
- A.检查程序运行时的性能指标
- B.编译并优化程序字节码
- C.拒绝危险程序保障内核安全← 正确答案
- D.自动生成探针挂载点
答案:C。验证器在程序加载阶段逐条评估指令,拒绝有死循环、越界访问、非法调用的程序,是 eBPF 安全模型的基石。B 是 JIT 编译器的职责,D 是 BCC 等工具链的功能。
Q3. kprobe 与 uprobe 的追踪对象有何区别?
- A.前者追踪内核函数后者追踪用户态函数← 正确答案
- B.前者追踪系统调用后者追踪库函数
- C.前者只能挂载网络事件后者是内存事件
- D.前者数量有限而后者可以无限挂载
答案:A。kprobe 插桩于内核函数(如 kmalloc),uprobe 插桩于用户态函数(如 malloc),这正是两种探针的分工核心。B 中系统调用只是内核函数的一类,不准确。C 和 D 的描述都不符合事实。
Q4. 内存监控中 Map 的典型用途是什么?
- A.存储摄像头拍摄的图像帧
- B.内核态与用户态共享统计信息← 正确答案
- C.缓存内核函数的调用结果
- D.保存用户态的日志文件内容
答案:B。Map 是 eBPF 程序与用户态程序之间的数据通道。eBPF 程序在内核侧把分配总量写入 Map,用户态脚本读取后展示,这是内存监控典型的数据流转方式。
Q5. 追踪应用层 malloc 调用应选择哪种挂载方式?
- A.kprobe 挂载到 __kmalloc
- B.uprobe 挂载到 malloc 符号← 正确答案
- C.tracepoint 挂载到 page_fault
- D.手动编写内核模块插桩
答案:B。malloc 是用户态 libc 提供的函数,必须用 uprobe 在用户进程内插桩。kprobe 只能看到内核态的 kmalloc,中间还隔着一层分配器,无法准确反映用户态申请行为。
Q6. 下列哪些是 eBPF 程序的安全约束?
- A.不允许无限循环← 正确答案
- B.只能调用内核提供的 helper 函数← 正确答案
- C.访问内存必须在验证器确认的范围内← 正确答案
- D.禁止使用 C 语言编写
答案:ABC。eBPF 通过三大约束保证安全:循环必须有限、只能调 helper、内存访问受到严格边界检查。C 语言是 eBPF 最主流的编写语言,完全可以用 C 写 eBPF 程序,因此 D 错误。
Q7. 定位内存泄漏的核心手段包括哪些?
- A.分配时保留调用栈记录← 正确答案
- B.释放时按指针配对删除← 正确答案
- C.定期扫描残留记录← 正确答案
- D.直接重启应用进程
答案:ABC。泄漏定位的“进场登记、出场核销、定期盘点”三步缺一不可。重启只是暂时缓解症状,没有解决根因,不属于定位手段。
Q8. 当一个进程 OOM 时,eBPF 能提供哪些线索?
- A.该进程近期的内存增长曲线← 正确答案
- B.触发大量分配的调用栈← 正确答案
- C.各内存桶的分配热力分布← 正确答案
- D.自动生成修复内存泄漏的补丁
答案:ABC。eBPF 实时采集增长曲线、调用栈和热力分布,这些数据帮助开发定位根因。但它只是观测工具,不负责生成修复补丁,那是编译器与人力的工作。
Q9. bpftrace 相比 BCC 的主要优势是什么?
- A.支持更复杂的数据结构
- B.单行命令快速现场分析← 正确答案
- C.可以提供更低的追踪开销
- D.内置更多内核补丁
答案:B。bpftrace 的设计目标就是“轻量巡检”,一行命令完成采样与统计。BCC 更适合需要写完整逻辑的复杂场景。二者底层都是 eBPF,追踪开销差异不大,C 不成立。
Q10. 关于观测开销描述正确的是?
- A.eBPF 探针完全不消耗任何资源
- B.事件高频时探针开销会显著增长← 正确答案
- C.kprobe 比 uprobe 一定更快
- D.Map 读写不会影响探针性能
答案:B。eBPF 开销虽低但并非为零,事件触发频率越高,处理逻辑的执行次数越多,开销自然上涨。开启 kprobe 探测 kmalloc 这种高频函数时要注意控制数据处理逻辑的复杂度。kprobe/uprobe 性能取决于上下文与场景,不存在绝对更快。