酝酿已久,终于得以撰写这篇关于eBPF的专题文章。本文旨在为读者简单介绍eBPF的核心功能及其应用方式。这项技术极其强大,显著提升了Linux系统的可观测性,尤其在BCC工具套件的加持下,系统内部运行细节变得清晰可见。然而,驾驭eBPF并非易事,它要求使用者不仅具备深厚的底层系统知识,还需拥有一定的开发能力。
eBPF,即“扩展伯克利数据包过滤器”,是一项允许开发者在不修改内核代码的情况下执行特定功能的内核技术。其起源可追溯至贝尔实验室开发的伯克利数据包过滤器(BPF),该技术最初用于捕获和过滤网络数据包。
为了满足Linux对更高效跟踪工具的需求,eBPF从DTrace中汲取了灵感。DTrace是一种广泛应用于Solaris和BSD操作系统的动态跟踪工具。与DTrace不同,早期的Linux在系统级可观测性方面存在局限,仅限于系统调用、库调用及特定函数框架。在此背景下,一群工程师在现有BPF后端的基础上进行扩展,旨在提供与DTrace类似的功能集合,由此eBPF应运而生。eBPF于2014年随Linux 3.18版本首次推出,但要充分发挥其潜力,通常需要Linux 4.4及以上版本。
与传统BPF仅限于网络过滤不同,eBPF的应用场景更为广泛,涵盖网络监控、安全过滤和性能分析等。eBPF允许用户空间应用程序将预设逻辑编译成字节码,在Linux内核中执行。当特定事件(即“挂钩”)发生时,内核将调用eBPF程序,这些事件包括系统调用和网络事件等。目前,最受欢迎的eBPF程序开发和调试工具链是BPF编译器集合(BCC),它基于LLVM和Clang。
eBPF与其他一些工具存在共通之处。例如,SystemTap是一个开源工具,通过动态加载内核模块来收集Linux内核的运行时数据,这与eBPF的工作方式有相似之处。DTrace则是一种动态跟踪和分析工具,同样可用于收集系统运行时数据,与eBPF和SystemTap功能相近。
eBPF、SystemTap和DTrace的对比凸显了各自的特点:
- eBPF:作为内核技术,应用场景广泛,通过动态加载和执行无损编译代码实现;其优点在于灵活、安全且多用途,但学习曲线较高,安全性依赖于编译器的正确性。 - SystemTap:以内核模块形式运行,用于系统性能分析和故障诊断,通过动态加载内核模块工作;功能强大,常提供可视化界面,但学习曲线同样较高,安全性依赖于内核模块的正确性。 - DTrace:动态跟踪和分析工具,用于系统性能分析和故障诊断,通过探针获取数据并分析;具备强大的功能和高性能,支持多种编程语言,但配置复杂,可能对系统性能造成较大影响。
综上所述,eBPF、SystemTap和DTrace都是强大的工具,能够有效收集和分析系统运行数据。
eBPF的用途广泛而强大,涵盖以下主要场景:
- 网络监控:eBPF能捕获网络数据包,并执行逻辑分析网络流量,例如监控异常流量并发出警报。 - 安全过滤:eBPF可用于网络数据包的安全过滤,如阻止或拦截恶意流量。 - 性能分析:eBPF能收集内核性能指标,并通过特定接口进行可视化呈现,从而帮助识别和优化性能瓶颈。 - 虚拟化:eBPF可应用于虚拟化环境,收集虚拟机性能指标并进行负载均衡,以优化资源利用率和系统稳定性。
简而言之,eBPF在网络监控、安全过滤、性能分析和虚拟化等领域具有广泛的应用潜力。
eBPF的工作原理主要分为加载、编译和执行三个步骤。
eBPF程序首先由用户态应用程序通过系统调用加载到内核中,在此过程中,eBPF程序的代码会被复制到内核空间。
随后,eBPF程序需经过编译和执行。通常,Clang/LLVM编译器会将程序转换为字节码,然后用户态的字节码被加载至内核。在此阶段,Verifier会对即将注入内核的程序进行一系列安全检查,以确保eBPF程序不会破坏内核的稳定性与安全性。这些检查包括分析代码,确保其没有恶意操作,如不当的系统调用或内存访问。一旦通过验证,eBPF程序便可在内核中正常运行,并通过即时(JIT)编译将通用字节码转换为机器特定指令集,以优化执行速度。
eBPF程序的架构图清晰地展示了其运行机制。
在内核中运行时,eBPF程序通常会“挂钩”到特定的内核事件上,以便在事件发生时被触发。这些“钩子”可以包括:
- 系统调用:当用户空间函数将控制权转移到内核时触发。 - 函数进入和退出:拦截对预设函数的调用。 - 网络事件:在接收到数据包时执行。 - Kprobe和Uprobe:附加到内核或用户函数上的探测点。
此外,eBPF Maps允许eBPF程序在不同调用之间保持状态,以便进行数据统计并与用户空间应用程序共享数据。eBPF Map本质上是一个键值存储,其值通常被视为任意数据的二进制块。它们通过带有BPF_MAP_CREATE参数的`bpf_cmd`系统调用创建,并通过文件描述符进行寻址。与Map的交互通过查找/更新/删除系统调用完成。
总结来说,eBPF通过动态加载、执行并验证无损编译代码来实现其功能。
以下是一个基于eBPF的性能分析示例,以分步形式展示其操作过程:
第一步:准备工作。首先,需确认内核已支持eBPF功能。这通常涉及在内核配置文件中启用eBPF相关选项并重新编译内核。您可以通过`ls /sys/fs/bpf`和`lsmod | grep bpf`命令检查eBPF支持情况。
第二步:编写eBPF程序。接着,编写eBPF程序以收集内核性能指标。eBPF程序可以使用C或Python编写,需要通过特定接口访问内核数据结构,并将收集到的数据保存到指定位置。
一个简单的Python-C混合示例(通过Python加载C代码到Linux内核): ```python #!/usr/bin/python3 from bcc import BPF from time import sleep
bpf_text = """ #include <uapi/linux/ptrace.h> BPF_HASH(stats, u32); int count(struct pt_regs *ctx) { u32 key = 0; u64 *val, zero=0; val = stats.lookup_or_init(&key, &zero); (*val)++; return 0; } """
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"]) b.attach_kprobe(event="tcp_sendmsg", fn_name="count")
name = { 0: "tcp_sendmsg" }
while True: try: for k, v in b["stats"].items(): print("{}: {}".format(name[k.value], v.value)) sleep(1) except KeyboardInterrupt: exit() ```
该eBPF程序通过定义`BPF_HASH`数据结构来存储统计结果(eBPF Maps),并通过捕获`tcp_sendmsg`事件实现网络数据包数量的实时统计。最后,它每秒输出一次统计结果。此示例较为基础,实际应用中可能需要更复杂的统计和分析逻辑。
第三步:运行eBPF程序。接下来,使用eBPF编译器将程序编译成内核可执行格式。BCC工具可以完成此任务,它能通过命令行将eBPF程序编译并加载到内核中。(如上文所示的Python程序中,`bcc`包已包含编译和加载功能。)
运行上述Python3程序的步骤如下: 安装`python3-bpfcc`:`sudo apt install python3-bpfcc`
**注意:在Python3环境下,不要使用`pip3 install bcc`。 对于Ubuntu 20.10及以上版本,建议通过源码安装,以避免编译问题。 **安装步骤**: ```bash apt purge bpfcc-tools libbpfcc python3-bpfcc wget -O bcc-src-with-submodule.tar.gz tar xf bcc-src-with-submodule.tar.gz cd bcc-0.24.0/ apt install -y python-is-python3 apt install -y bison build-essential cmake flex git libedit-dev libllvm11 llvm-11-dev libclang-11-dev zlib1g-dev libelf-dev libfl-dev python3-distutils apt install -y checkinstall mkdir build cd build/ cmake -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3 .. make sudo checkinstall ``` 将Python程序(例如保存为`netstat.py`)保存到本地。通过以下命令运行程序: ```bash $ chmod +x ./netstat.py $ sudo ./netstat.py ```
程序启动后,控制台会输出网络数据包的统计信息。通过`Ctrl+C`组合键可终止程序。
再看一个更复杂的示例,用于计算TCP发包时间(参考自GitHub上的相关程序):
```python #!/usr/bin/python3 from bcc import BPF import time
bpf_text = """ #include <uapi/linux/ptrace.h> #include <net/sock.h> #include <net/inet_sock.h> #include <bcc/proto.h>
struct packet_t { u64 ts, size; u32 pid; u32 saddr, daddr; u16 sport, dport; };
BPF_HASH(packets, u64, struct packet_t);
int on_send(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size) { u64 id = bpf_get_current_pid_tgid(); u32 pid = id;
struct packet_t pkt = {}; pkt.ts = bpf_ktime_get_ns(); pkt.size = size; pkt.pid = pid; pkt.saddr = sk->__sk_common.skc_rcv_saddr; pkt.daddr = sk->__sk_common.skc_daddr; struct inet_sock *sockp = (struct inet_sock *)sk; pkt.sport = sockp->inet_sport; pkt.dport = sk->__sk_common.skc_dport;
packets.update(&id, &pkt); return 0; }
int on_recv(struct pt_regs *ctx, struct sock *sk) { u64 id = bpf_get_current_pid_tgid(); u32 pid = id;
struct packet_t *pkt = packets.lookup(&id); if (!pkt) { return 0; }
u64 delta = bpf_ktime_get_ns() - pkt->ts;
bpf_trace_printk("tcp_time: %llu.%llums, size: %llu\\n", delta/1000, delta%1000%100, pkt->size);
packets.delete(&id); return 0; } """
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"]) b.attach_kprobe(event="tcp_sendmsg", fn_name="on_send") b.attach_kprobe(event="tcp_v4_do_rcv", fn_name="on_recv")
print("Tracing TCP latency... Hit Ctrl-C to end.") while True: try: (task, pid, cpu, flags, ts, msg) = b.trace_fields() print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg)) except KeyboardInterrupt: exit() ```
该程序通过捕获每个数据包的发送和接收时间戳来计算传输延迟。在`tcp_sendmsg`事件时记录发送时间,在`tcp_v4_do_rcv`事件时记录接收时间,然后通过时间戳之差计算传输时间。从这两个示例可以看出,eBPF编程的基本模式是在Python中将C语言代码挂载到内核的特定事件上。坦率地说,编写此类代码具有挑战性,需要应对许多复杂且细致的问题,对普通使用者而言颇具门槛。幸运的是,已存在大量由社区贡献的eBPF程序,例如GitHub上bcc库的`tools`目录下有丰富的实现可供参考。
BCC(BPF Compiler Collection)是一套开源工具集,它利用BPF技术在Linux系统上进行系统级性能分析和监测。BCC包含了众多实用工具,例如:
- `bcc-tools`:包含了多种常用的BCC工具。 - `bpftrace`:一种高级语言,用于编写和执行BPF程序。 - `tcptop`:实时监控和分析TCP流量的工具。 - `execsnoop`:用于监测进程执行情况的工具。 - `filetop`:实时监控和分析文件系统流量的工具。 - `trace`:用于跟踪和分析函数调用的工具。 - `funccount`:统计函数调用次数的工具。 - `opensnoop`:监测文件打开操作的工具。 - `pidstat`:监测进程性能的工具。 - `profile`:分析系统CPU使用情况的工具。
这张广为人知的图片展示了eBPF能实现的功能之广,让内核内部的任何事件都无所遁形。
**延伸阅读**
关于eBPF的经典文章和书籍包括:
- Brendan Gregg的《BPF Performance Tools: Linux System and Application Observability》一书,它全面涵盖了eBPF的基础知识和实践应用。 - eBPF官方网站,由Cilium建立。 - Cilium的BPF和XDP参考指南。 - BPF文档。 - BPF设计问答记录。 - GitHub上的Awesome eBPF项目。
最后,揭晓一个小彩蛋。鉴于最近ChatGPT的流行,我尝试利用它来辅助撰写这篇报道。最初,我让ChatGPT列出提纲,并根据提纲生成文章内容,并进行查证。
