想象你是医院院长,凌晨两点接到ICU警报:核心系统(一位重要病人)CPU飙到99%,心跳如雷,眼看要撑不住了。你需要回答三个问题:哪里在发热?为什么发热?怎么治? 第一问,护士递来一张「全身热成像报告」——这就是火焰图:它把人体温度分布画成一座彩色山丘,越宽的色块代表越热的部位,一眼锁定发烧点。第二问,以前要开刀插管才能探进体内(改内核模块),如今一颗「微型传感器胶囊」——eBPF——吞下去就能在血管里实时播报数据,不开刀、不伤身。第三问,是靠医生手里的「体检工具箱」BCC,或亲手写一张「检查清单」bpftrace,把可疑环节逐个查清。 这篇文章,带你从这张热成像报告出发,完整学会这套「内窥镜体检」流程。你会发现,曾经让人抓狂的性能排查,其实可以像查房一样从容——先看哪里热,再探为什么,最后对症下药。

岗位关联

对AI从业者而言,火焰图与eBPF是通往深度性能分析、内核级可观测性的门票。掌握它们,你就能在生产环境精准定位AI推理、向量检索、微服务链路中的每一毫秒损耗,成为团队里能「缝合内核与业务」的关键角色。

目录
  1. 可视化演示
  2. 火焰图原理:先看哪里在发热
  3. eBPF核心原理:吞下一颗微型传感器胶囊
  4. BCC工具集:护士站的现成体检仪器
  5. bpftrace脚本:手写一张检查清单
  6. off-CPU与锁竞争:查一查病人的候诊时间
  7. 生产环境实践:ICU里的低开销监护仪
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示eBPF追踪架构:Kprobe/Trace/Uprobe/XDP四种探针→内核(Kern)→BCC/bpft/Cil/Pixie工具链→Off-CPU和Lock分析。

本节配有交互动效(进入视口后自动加载,约 3KB)。

eBPF追踪与火焰图 eBPF四种探针:Kprobe(内核函数)→Trace(跟踪点)→Uprobe(用户态)→XDP(网络),全部挂载到内核(Kern)安全执行 工具链:BCC(Python封装)→bpft(bpftrace脚本)→Cil(Cilium网络)→Pixie(可观测性),Off-CPU+Lock分析定位性能瓶颈 eBPF: Hook → BPF Program → Maps → Userspace, Safe in-kernel execution

火焰图原理:先看哪里在发热

回到ICU的清晨。护士递来的热成像报告不是普通照片,而是一幅由无数彩色条块垒成的「山形图」。山脚是入口(main函数),山顶是CPU此刻正在埋头干活的函数。每条色块的宽度,代表它在你视线中出现的比例——就像体温计每隔5秒量一次温,某个部位被测到的次数越多,它在图上就越宽。看到最宽最亮的色块,你就知道:发热点在这里。

在计算机世界里,这张热成像报告就是火焰图。它的原始材料来自「栈采样」:性能分析器(比如perf)每隔一个极短的时间间隔(如1毫秒),就「偷看一眼」CPU正在执行什么函数、这个函数经过哪些调用链到达,并把这条调用链记录下来。假设系统运行10秒、采样了10000次,某条调用链出现了3000次,那么它在火焰图上就占据30%的宽度。采样次数越多,这张图越接近真相。

火焰图有三条阅读规则:x轴不是时间线,而是「采样占比」(所有色块从底到顶的宽度总和代表100%);y轴是从下到上的调用深度,栈顶是「此刻在干活」的函数;颜色只是为了区分不同函数的热区,没有任何性能含义——别被红色吓住,也别以为绿色就健康。生成一张图只需三步:用perf record采样、把原始记录折叠成「调用链 次数」、再用flamegraph.pl渲染成SVG。即使采样文件超过几百兆,折叠后也不过几兆。

回到病房:你盯着热成像报告最宽的那块「红色山脊」——parser解析模块。它被采样到的比例遥遥领先,说明CPU大部分时间都在这里燃烧。但热成像只能告诉你「哪里热」,要回答「为什么热」,你得往身体里放传感器——下一节的主角eBPF正式登场。

P(热点函数)=N热点函数N总采样×100%\displaystyle P(\text{热点函数})=\frac{N_{\text{热点函数}}}{N_{\text{总采样}}}\times 100\%
某个函数在火焰图上的宽度占比,等于它被采样到的次数除以全部采样次数。
  1. 分子热点函数的采样次数:统计目标函数在采样中被「抓住」的次数,例如 parse 被碰到 3000 次。
    N热点函数N_{\text{热点函数}}
  2. 分母总采样次数:整个观察期间的采样总数,例如 10000 次。
    N总采样N_{\text{总采样}}
  3. 最终占比两者相除再乘 100%,得到横轴宽度百分比,30% 就对应 30% 的宽度。
    N热点函数N总采样×100%\frac{N_{\text{热点函数}}}{N_{\text{总采样}}}\times 100\%

eBPF核心原理:吞下一颗微型传感器胶囊

热成像找到了发热点,但医生还想知道:CPU在parse里究竟做了什么、系统调用的参数是什么、每次调用花了多久?以前的办法是「开刀」——写一个内核模块,修改内核代码,风险极高,稍有不慎整台机器就蓝屏。现在有了新选择:一颗名为eBPF的「微型传感器胶囊」。病人吞下它,不用开刀,胶囊随血液流动,在指定位置粘住、实时上报数据,副作用几乎为零。

eBPF的全称是extended Berkeley Packet Filter(扩展的伯克利包过滤器),它允许用户在Linux内核中运行一段「受到严格安全审查的小程序」。这段程序不是随便写的——它要经过一个叫「验证器」的守门人逐条审查:循环有没有边界、指针有没有越界、会不会把内核搞崩。审查通过后,由JIT编译器翻译成原生指令,挂到内核的「钩子」上,比如系统调用入口(tracepoint)、任意内核函数(kprobe)、用户态函数(uprobe)。

一旦事件发生——比如某个进程发起read系统调用——胶囊立刻在现场就地处理数据:可以过滤、计数、聚合,只把「有分析价值」的少量结果通过ring buffer或map通道送出内核到用户态。这与过去把海量原始事件一股脑丢给用户态工具的设计完全不同:数据在源头就被「消化」了,开销大幅降低。这也是eBPF能用于高频线上监控的根本原因。

胶囊虽小,五脏俱全:验证器是药监局的批文,JIT是胶囊加工厂,ring buffer是它的无线天线。现在我们知道传感器如何工作,下一站是护士站里那排现成的「体检仪器」——BCC工具集。

有效事件率=N通过过滤的事件N触发总数\displaystyle \text{有效事件率}=\frac{N_{\text{通过过滤的事件}}}{N_{\text{触发总数}}}
eBPF程序在事件源头完成过滤,最终送出内核的事件占比越高,说明过滤设计越聪明,用户态负担越轻。
  1. 分子通过过滤的事件数:经过eBPF程序处理并认定「值得上报」的事件数量。
    N通过过滤的事件N_{\text{通过过滤的事件}}
  2. 分母触发总数:内核钩子上发生的全部事件数量,无论是否被过滤。
    N触发总数N_{\text{触发总数}}
  3. 有效事件率两者之比。若过滤得当,可能只有 0.1% 的事件被送出,用户态几乎无压力。
    N通过过滤的事件N触发总数\frac{N_{\text{通过过滤的事件}}}{N_{\text{触发总数}}}

BCC工具集:护士站的现成体检仪器

胶囊技术虽好,但你总不能在手术台前现场造一粒。好在护士站里已经摆好一排「体检仪器」——BCC(BPF Compiler Collection)。它们是预先封装好的eBPF程序,像心电图机、血压仪一样,握住手柄、按下按钮就能用。你不需要懂内核编译细节,甚至不需要写一行代码。

BCC用Python包裹了C语言的eBPF程序,把「编写→编译→加载→读数据→出结果」整个链路封装成一个个命令行工具。排查性能问题时,你往往会先用它们做初筛:CPU排队怎么样?磁盘延迟高不高?文件读写是不是热点?进程有没有在频繁创建销毁?

几个常用工具:runqlat统计线程在CPU运行队列里排队的时间,回答「有没有在等CPU」;biolatency追踪块设备I/O延迟分布,回答「磁盘为什么慢」;filetop按文件维度统计读写频率与耗时;execsnoop监视进程创建事件,快速发现异常短命进程。这类工具的输出通常是「延迟直方图」,直接看p99等百分位就能判断:99%的请求延迟都在100毫秒以内,但最后1%可能飙到3秒——那1%就是你要追的「长尾病人」。

这些仪器摸清了病人整体状态:CPU排队有点深、磁盘偶尔抖动。但有些更刁钻的检查(比如只看某种特定系统调用、统计某个容器的阻塞情况),现成仪器不管用。下一件武器来了:自己写检查清单——bpftrace。

p99=inf⁡{x  ∣  F(x)≥0.99}\displaystyle p_{99} = \inf\{x \;|\; F(x) \ge 0.99\}
p99是「99%的样本都小于等于它」的阈值,用来观察长尾延迟是否失控。
  1. 分布函数 F(x)表示样本中小于 x 的比例,例如90%的请求延迟不超过80毫秒。
    F(x)F(x)
  2. 下确界 inf在满足条件的临界值里取「最小」的哪一个,恰好让比例跨过0.99。
    inf⁡{x  ∣  F(x)≥0.99}\inf\{x \;|\; F(x) \ge 0.99\}
  3. p99 的含义这个临界 x 就是 p99——只有 1% 的请求比它更慢。

bpftrace脚本:手写一张检查清单

当现成仪器不够用,主治医生会从白大褂口袋抽出一张空白检查单,边看边写:「如果遇到do_sys_open这个动作,就记下是哪个进程、打开什么文件、耗时多少。」这张检查单,就是bpftrace。它不需要像BCC那样写完整的Python+Clang工程,只需一两行看似简单却十分强大的探针指令。

bpftrace是一种专为eBPF设计的脚本语言,语法接近awk:探针点 /过滤条件/ { 动作 }。探针点告诉内核传感器粘在哪(kprobe:do_sys_open表示拦截内核函数do_sys_open),过滤条件决定哪些事件值得处理(比如pid == 1234),动作描述事件发生后的操作(统计、打印、聚合)。

一条经典的一行式命令:bpftrace -e 'kprobe:do_sys_open { @[comm] = count(); }'。意思是:内核每次执行do_sys_open,就把当前进程名作为key累加一次,Ctrl+C结束时自动打印「谁在频繁打开文件」的排行表。更复杂的脚本用三段结构组织:BEGIN{}做初始化(进场摆好仪器)、探针{}做事件处理(每遇一个症状记一笔)、END{}做汇总报告(离场前朗读总结)。聚合数据存放在@开头的map中,既能count、sum,也能hist画直方图。

检查单越写越顺手:一次查看某个容器里所有read请求的大小分布,一次追踪某个进程的socket连接来源。但医生心里还有一块阴影——病人有不少时间既不在CPU上,也没闲着,就像门诊楼里排长队的病人干等叫号。这就是下一节要处理的off-CPU问题。

count(事件)=∑i=1NI(事件i 满足条件)\displaystyle \text{count}(\text{事件})=\sum_{i=1}^{N} \mathbb{I}(\text{事件}_i \ \text{满足条件})
每条符合条件的事件使计数器加一,最终得到该事件的发生总数。
  1. 求和符号把从1到N的所有事件逐一检查并相加。
    ∑i=1N\sum_{i=1}^{N}
  2. 指示函数当事件满足条件时为1,不满足为0,它像一条「闸机」。
    I(事件i 满足条件)\mathbb{I}(\text{事件}_i \ \text{满足条件})
  3. 聚合结果把所有1累加起来,得到count总数,这就是bpftrace中count()的底层逻辑。

off-CPU与锁竞争:查一查病人的候诊时间

ICU里有位病人很诡异:体温不高(CPU利用率不饱和),但病症很重(吞吐量上不去)。你把热成像报告翻来覆去,也找不到哪个色块特别宽。这时老师傅提醒:别再死盯「正在干活」的时间,去看看「排队等待」的时间——就像门诊楼候诊椅上坐着的病人,他们没在诊室里,但候诊时间同样算进了「总就医时间」里。off-CPU分析,就是去调查这些候诊椅。

火焰图只能展示on-CPU(CPU上活跃执行)的时间;但当线程在等锁、等磁盘I/O、等网络,或被调度器踢下去时,CPU上根本看不到它。off-CPU分析通过追踪「线程被切换出去的瞬间」,记录它下线的时间点和原因,把等待时间按阻塞栈聚合成一张「off-CPU火焰图」。BCC的offcputime工具一条命令就能打出这张图。

锁竞争是off-CPU时间的大户。多线程同时抢一把锁,没抢到的线程只能自旋或休眠,干等别人释放。常用分析视角有三个:mutexlock/lockstat统计互斥锁等待时间与竞争次数;runqlat看线程在就绪队列里的排队时间,判断是不是CPU核数不够;线程切换追踪(sched探针)观察context switch高频发生在哪些线程上。分析套路是「三步走」:先看整体CPU利用率判断是「CPU忙」还是「CPU闲」,再用on-CPU火焰图找热点,最后用off-CPU火焰图找等待,两者拼出完整时间账。

原来病人的问题不是心脏不够强(CPU不忙),而是挂号窗口只有一扇(单把锁成瓶颈)——护士们都在排队等一个盖章。补一个挂号窗口(拆锁/无锁化),体温立刻回升到正常值。现在,医生准备把这套查房流程固化下来,变成ICU里7×24小时的监护系统——这就是生产环境实践。

Toff-CPU=∑iT阻塞事件i\displaystyle T_{\text{off-CPU}}=\sum_{i} T_{\text{阻塞事件}_i}
off-CPU总时长等于所有阻塞事件(锁等待、磁盘等)的等待时长之和。
  1. 单次阻塞时长记录线程从被切出CPU到恢复运行的时间间隔。
    T阻塞事件iT_{\text{阻塞事件}_i}
  2. 求和符号把无数次阻塞事件的时间全部累加。
    ∑i\sum_{i}
  3. 总时间等式总的 off-CPU 时长加上 on-CPU 时长,才等于真实的总耗时。
    Toff-CPUT_{\text{off-CPU}}

生产环境实践:ICU里的低开销监护仪

现在医生要建一间真正的ICU监护室:病人在里面绝不能被打扰,监护仪必须安静、低功耗、7×24小时工作。生产环境的服务器就是这样的病人。传统的perf像一台笨重CT机:需要停机预约(要重启加载模块)、动静大(采样开销可观)、报告还得离线分析。而eBPF工具像一枚贴皮肤的「动态心电贴片」:随贴随取、实时出数、几乎无感。

两者对比明显:perf以周期采样为主,适合事后的深度剖析;eBPF系列工具以事件驱动为主,适合实时监控与快速定位。但eBPF并非天然「零开销」——如果追踪的事件频率极高(比如每次CPU调度、每条网络包),处理开销也会迅速累积。低开销的秘诀有三条:加过滤条件(只在满足条件时处理)、降低采样/跟踪频率、缩小跟踪时长(监控窗口按需开启)。

生产环境中一个可落地的排查流程:先瞄整体指标(CPU利用率、load、QPS)判断方向;再用BCC的runqlat、biolatency初筛,排除排队和IO问题;接着用perf采样几分钟生成火焰图,定位on-CPU热点;然后用offcputime生成off-CPU火焰图,定位锁竞争与IO等待;最后修复后重复前两步验证指标回落。在云原生场景下,eBPF更是可观测性的关键技术:无需侵入应用代码,按PID、容器、Pod维度采集指标,与Prometheus/Grafana打通绘制长期趋势,真正做到「发布上线后心里有底」。

从一张热成像报告起步,到传感器胶囊、体检工具箱、手写检查单、候诊时间排查,再到ICU监护仪——你的系统体检方法论已经闭环。下一次业务方喊「慢」的时候,你已知道自己该先看哪张图、再按哪个按钮,而不是慌慌张张重启大法。

额外开销=P采样时−P基线P基线×100%\displaystyle \text{额外开销}=\frac{P_{\text{采样时}}-P_{\text{基线}}}{P_{\text{基线}}}\times 100\%
开启监控工具前后的CPU利用率差值占比,用于评估该工具对生产环境的影响程度。
  1. 分子采样时与基线的差值:开启工具后CPU利用率相比未开启时上升了多少。
    P采样时−P基线P_{\text{采样时}}-P_{\text{基线}}
  2. 分母基线CPU利用率:没有监控工具时系统的自然使用率。
    P基线P_{\text{基线}}
  3. 百分比差值除以基线再乘100%,小于3%通常被认为可接受。
    P采样时−P基线P基线×100%\frac{P_{\text{采样时}}-P_{\text{基线}}}{P_{\text{基线}}}\times 100\%

AI 应用场景

云原生微服务的发热点定位

阶段:模型推理服务性能调优

解决的问题:传统top/htop只能看到进程级CPU占用,无法定位到具体函数。在K8s集群中,某个推理Pod的CPU被打满,但没人知道是哪一个线程里的哪一段代码在烧CPU。用eBPF采集调用栈并渲染成火焰图,能直接定位到「Pod内某个线程的某个函数」。

具体案例:某推荐系统的embedding查询服务CPU爆满,perf采样生成火焰图后发现热点集中在向量相似度计算的循环里,属于算法层可优化代码。优化后QPS提升40%,P99延迟下降62%。

数据库锁竞争与延迟抖动排查

阶段:数据库性能诊断

解决的问题:高并发下数据库延迟抖动,难以判断是锁竞争、磁盘瓶颈还是网络问题。插桩分析每个SQL事务的等待事件,给出锁等待时间与持有锁的调用栈。

具体案例:某订单系统的MySQL在晚高峰出现大量「锁等待超时」。用offcputime生成off-CPU火焰图,发现等待集中在某条UPDATE语句持有行锁期间,其他事务排队等待。通过调整索引减少锁范围,锁等待时间下降90%。

分布式训练的数据喂不饱问题

阶段:分布式训练任务

解决的问题:多卡训练时GPU利用率忽高忽低,怀疑数据加载线程在等什么。用bpftrace自定义探针追踪数据加载线程的off-CPU时间,快速定位阻塞来源。

具体案例:训练集群中GPU利用率只有60%,用bpftrace统计数据加载线程的调度延迟,发现大量时间花在NFS读取的锁等待上。改用本地缓存后在相同batch下训练吞吐提升30%。

Python 演示

python60 行
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""模拟栈采样并生成火焰图 SVG用随机权重模拟采样过程, 统计热点函数,输出一份简化的火焰图 SVG 文件.""" import random COLORS = ["#C7D2E4", "#818CF8", "#10B981", "#F43F5E"]  def sample_once():    """模拟一次采样, 返回一条随机深度与频率的调用链"""    idx = random.randint(0, 3)    chains = [        "web_server>worker>db_query>json_parse",        "web_server>worker>db_query",        "web_server>worker",        "web_server"    ]    return chains[idx]  def main():    random.seed(42)    total = 1000    stats = {}    for _ in range(total):        chain = sample_once()        stats[chain] = stats.get(chain, 0) + 1     lines = ['<svg viewBox="0 0 640 200" xmlns="http://www.w3.org/2000/svg">']    y = 160    for i, chain in enumerate(["web_server>worker>db_query>json_parse",                               "web_server>worker>db_query",                               "web_server>worker",                               "web_server"]):        n = stats.get(chain, 0)        w = int(640 * n / total)        lines.append(            f'<rect x="0" y="{y}" width="{w}" height="30" '            f'fill="{COLORS[i]}" stroke="#fff"/>'        )        lines.append(            f'<text x="{w + 8}" y="{y + 20}" font-size="12" fill="#0F172A">'            f'{chain.split(">")[-1]} {round(n / total * 100)}%</text>'        )        y -= 30    lines.append("</svg>")     with open("flamegraph.svg", "w") as f:        f.write("\n".join(lines))    print("flamegraph.svg 已生成")    for k, v in sorted(stats.items(), key=lambda x: -x[1]):        print(f"{v:4d} {k}")  if __name__ == "__main__":    main()
目的:用随机采样模拟「性能分析器每隔一段时间记录一次调用栈」的过程,统计各调用链的命中次数,并生成一份简化的火焰图SVG。
  1. chains = ["web_server>worker>db_query>json_parse", ...]预置四条由深到浅的调用链,模拟真实系统中不同调用栈出现的概率差异。
  2. stats[chain] = stats.get(chain, 0) + 1每次采样命中某条调用链就计数加一,对应「采样次数越多,占比越大」的核心原理。
  3. w = int(640 * n / total)把采样占比换算成SVG矩形宽度,这就是火焰图「横轴宽度=采样占比」的数学表达。
  4. with open("flamegraph.svg", "w") as f把生成的SVG写入文件,用浏览器打开即可看到火焰图效果。
  5. random.seed(42)固定随机种子,保证每次运行结果一致,便于复现实验。

常见误区

误区我盯着火焰图里最宽的那块区域,以为它横跨的时间段最长,优化它一定最有效。结果优化后整体耗时几乎没变。

正解火焰图的横轴是「采样计数占比」,不是真实时间线。它回答的是「CPU花在这条调用链上的时间比例有多少」,而不是「这段代码在哪个时间段运行」。同样宽的色块可能均匀散布在10分钟内,也可能集中在开头1秒。盲目优化一个「看起来宽」但低频的短任务,收益往往很小。

为何常见:人看到横向条形图形会本能联想到时间轴,尤其是有Gantt图或直方图经验的人,更容易先入为主。

误区火焰图里红色块最宽,一定是性能最差、最该先处理的地方。我每次都优先优化红色函数。

正解火焰图的颜色只是视觉上区分相邻函数,方便追踪,与性能没有关系。红色块可能只是一个低频初始化函数,而蓝色块才是真正需要优化的热点。判断标准永远只有一条:宽度占比,与颜色无关。

为何常见:红=危险的配色直觉深植人心,可视化工具也常把高占用标红,导致观众形成「红色=问题」的条件反射。

误区既然能往内核里塞程序,我是不是可以写一个遍历所有进程内存的eBPF程序,做任意想做的事?

正解eBPF程序要经过验证器的严格审查:循环必须有界、指针不能随意解引用、栈大小受限、只能调用白名单辅助函数。它只能做「有界、安全、非阻塞」的观测类工作,不能随心所欲访问内核数据。

为何常见:把「在内核运行代码」等同于「拥有全部内核权限」,忽略了沙箱机制这一核心设计,也低估了验证器的约束力。

误区学会off-CPU分析之后,我觉得火焰图可有可无,反正off-CPU能抓到各种等待问题。

正解两者解决完全不同的问题:火焰图看「CPU忙在哪」,off-CPU看「线程空等在哪」。当CPU利用率极高时,off-CPU时间趋近于零,必须靠火焰图;而当CPU利用率低但吞吐差时,off-CPU才能找到排队等待的根因。两张图是互补的双翼,缺一不可。

为何常见:学完一个新工具后,容易「手里有锤子看什么都是钉子」,忽略了工具的适用边界。

误区网上都说eBPF开销极低,于是我放心地开着bpftrace追踪所有系统调用,跑了一整周。

正解低开销的前提是「选择性地观测」。如果你在每次系统调用、每次调度、每个网络包上都挂探针,事件频率可达每秒百万级,即使单个探针只花几百纳秒,总开销也能累积到十几个百分点。真正的低开销来自过滤条件、采样或按需开启的机制,而非eBPF本身。

为何常见:只听到「低开销」的结论,没理解低开销其实来自「事件源头过滤」这个设计前提。

面试题

Q1. 火焰图中,横轴的宽度代表什么?

单选题 · easy

  • A.函数执行的真正时长
  • B.函数被采样次数占比← 正确答案
  • C.函数源代码的行数
  • D.函数调用栈的深度

答案:B。火焰图横轴宽度=该函数被采样到的次数除以总采样次数,回答「CPU花在此处的时间比例」。它既不是真实秒数,也不是代码行数或调用深度。

Q2. 火焰图的纵轴从下到上表示什么?

单选题 · easy

  • A.函数调用的嵌套深度← 正确答案
  • B.函数执行耗时长短
  • C.函数拥有的调度优先级
  • D.函数所在CPU核心编号

答案:A。y轴是调用栈从入口到栈顶的嵌套深度,越往上越接近CPU正在执行的叶子函数。耗时、优先级、CPU编号都不是y轴含义。

Q3. 火焰图中某函数条呈现红色,说明什么?

单选题 · easy

  • A.该函数占用CPU最高
  • B.该函数存在性能问题
  • C.仅用于区分不同函数← 正确答案
  • D.该函数拥有最高优先级

答案:C。火焰图颜色只是视觉区分——相邻条块颜色不同方便追踪,与性能、优先级无关。判断热点永远看宽度占比。

Q4. eBPF程序被允许进入内核前,必须通过什么检查?

单选题 · mid

  • A.杀毒软件扫描检查
  • B.验证器安全审查通过← 正确答案
  • C.编译器优化级别检查
  • D.内核版本兼容检测

答案:B。验证器会逐条审查代码:循环是否有界、指针是否越界、是否只能调用白名单辅助函数。通过后才由JIT编译进内核,这是eBPF安全性的根基。

Q5. 用 bpftrace 观察「谁在频繁打开文件」,合适的探针是?

单选题 · mid

  • A.拦截打开文件的系统调用← 正确答案
  • B.拦截内核函数 do_fork
  • C.跟踪调度器切换事件
  • D.跟踪用户态 read 函数

答案:A。打开文件在内核中对应do_sys_open系统调用路径,用kprobe:do_sys_open拦截即可。do_fork管进程创建,sched_switch管线程切换,用户态read函数管读数据。

Q6. off-CPU 分析主要回答什么问题?

单选题 · mid

  • A.哪个函数消耗了 CPU
  • B.线程在等待什么未上 CPU← 正确答案
  • C.内存具体分配了多少
  • D.网络报文如何被路由

答案:B。off-CPU聚焦「线程被切出CPU后等什么」——等锁、等磁盘、等网络。A属于on-CPU火焰图的范畴,C是内存问题,D是网络路径问题。

Q7. 关于 eBPF 的数据通路,以下说法正确的是?

单选题 · hard

  • A.所有事件必须全量送到用户态
  • B.事件在内核态过滤后送出← 正确答案
  • C.数据只能通过文件系统传输
  • D.用户态无法读取 eBPF 的 map

答案:B。eBPF的核心设计是在内核态就地过滤、聚合,只把少量结果经ring buffer或map送出,避免海量数据拷贝到用户态。这也是它低开销的关键。

Q8. 哪些是 BCC 工具集中常见的诊断工具?

多选题 · mid

  • A.运行队列延迟工具← 正确答案
  • B.块设备延迟工具← 正确答案
  • C.文件热点监控工具← 正确答案
  • D.数据库查询优化工具

答案:ABC。A对应runqlat,B对应biolatency,C对应filetop,它们都是BCC自带的现成eBPF工具。D是传统数据库调优手段,不属于BCC工具集。

Q9. 关于火焰图与 eBPF 的关系,正确的理解是?

多选题 · hard

  • A.火焰图可来自 perf 采样← 正确答案
  • B.eBPF 可生成 off-CPU 火焰图← 正确答案
  • C.火焰图能显示锁等待时间
  • D.eBPF 工具只看 CPU 事件

答案:AB。perf是生成火焰图的经典采样工具;eBPF用offcputime等工具生成off-CPU火焰图。普通火焰图显示不了锁等待(那是off-CPU火焰图的用途);eBPF既能看CPU事件也能看调度、IO等事件。

Q10. 一个 eBPF 程序要合法运行,需要满足哪些条件?

多选题 · extreme

  • A.循环必须有边界约束← 正确答案
  • B.只能调用白名单函数← 正确答案
  • C.指针操作受到安全限制← 正确答案
  • D.必须能访问任意内核文件

答案:ABC。验证器要求循环有界(防止死循环拖垮内核)、只能调用白名单辅助函数(防止滥用内核API)、指针操作受严格限制(防止越界读写)。任意访问内核文件恰好是验证器所禁止的,D正好说反。