← 返回新闻列表

eBPF技术深度解析:揭秘Linux内核的强大观测与扩展能力

eBPF(扩展伯克利数据包过滤器)作为一项革命性的Linux内核技术,正日益展现其在系统观测、安全防护及性能优化方面的强大潜力。本文将深入探讨eBPF的起源、工作原理及其广泛应用场景,并通过具体案例展示其在网络监控和性能分析中的实践,旨在为具备底层系统知识和开发背景的技术人员提供全面而详尽的解读。

文 / 编辑部 · 2022/12/10 · 阅读约 13 分钟

分享:
eBPF技术深度解析:揭秘Linux内核的强大观测与扩展能力

酝酿已久之后,终于有机会提笔撰写一篇关于eBPF的文章。本文旨在深入浅出地介绍这项技术的核心功能和实践方法。eBPF的强大之处在于它极大地提升了Linux操作系统的可观测性,尤其在BCC工具集的辅助下,系统内部运行状况几乎能被洞察无遗。需要指出的是,这项技术对操作人员的底层系统知识和开发能力有较高要求,并非普通运维或系统管理员能够轻易驾驭。

eBPF,即“扩展伯克利数据包过滤器”,是一项允许开发人员在无需修改内核源代码的前提下,在内核空间执行特定功能的创新技术。它的概念脱胎于贝尔实验室开发的伯克利数据包过滤器(BPF),后者最初主要用于网络数据包的捕获与过滤。

为了满足对更高效Linux跟踪工具的需求,eBPF从DTrace中汲取灵感。DTrace是Solaris和BSD系统上广泛使用的动态跟踪工具,而早期的Linux在系统洞察力方面相对受限,只能通过系统调用、库调用和特定函数框架进行观察。在此背景下,一群工程师在现有BPF的基础上,着手扩展其后端,力求实现与DTrace相匹敌的功能集,由此催生了eBPF。eBPF于2014年在Linux 3.18版本中首次有限发布,但要充分发挥其潜力,通常需要Linux 4.4及以上版本。

与传统的BPF仅限于网络过滤不同,eBPF的应用场景更为宽广,涵盖网络监控、安全过滤以及性能分析等多个领域。eBPF允许用户空间的应用程序将预期的逻辑以字节码形式提交至Linux内核执行。当特定的“钩子”事件(如系统调用、网络事件等)发生时,内核便会触发这些eBPF程序。当前,编写和调试eBPF程序最流行的工具链是基于LLVM和Clang的BPF编译器集合(BCC)。

与eBPF相似的工具还包括SystemTap和DTrace。SystemTap是一个开源工具,同样通过动态加载内核模块来收集Linux内核的运行时数据。DTrace则是一款功能强大的动态跟踪与分析工具,能够收集系统运行时数据。它们在设计理念和工作方式上各具特色。

以下表格清晰地对比了eBPF、SystemTap和DTrace这三款工具的异同:

| 工具 | eBPF | SystemTap | DTrace | | --------- | --------------------------------- | ------------------------------- | ------------------------------------ | | 定位 | 内核技术,多应用场景 | 内核模块 | 动态跟踪与分析工具 | | 工作原理 | 动态加载执行无损编译代码 | 动态加载内核模块 | 动态插接分析器,通过探针获取数据 | | 常见用途 | 网络监控、安全过滤、性能分析等 | 系统性能分析、故障诊断等 | 系统性能分析、故障诊断等 | | 优点 | 灵活、安全、多应用场景 | 功能强大、可视化界面 | 功能强大、高性能、支持多种编程语言 | | 缺点 | 学习曲线陡峭,安全性依赖编译器 | 学习曲线陡峭,安全性依赖内核模块 | 配置复杂,对系统性能影响较大 |

从上述比较可以看出,eBPF、SystemTap和DTrace都是用于系统数据收集与分析的强力工具,各有侧重。

eBPF极为灵活且功能强大,广泛应用于各类场景。其主要用途包括:

网络监控:eBPF能够捕获并分析网络数据包,进而对网络流量进行深入分析,例如实时监测异常流量并发出警报。

安全过滤:eBPF可用于实施网络数据包的安全过滤,例如阻止恶意流量传播或对其进行有效拦截。

性能分析:eBPF可收集内核性能指标,并通过特定接口可视化呈现,从而帮助识别和优化内核性能瓶颈。

虚拟化:在虚拟化环境中,eBPF可以收集虚拟机性能指标并进行负载均衡,有效提升系统性能和稳定性。

综上所述,eBPF在网络监控、安全过滤、性能分析和虚拟化等多个应用领域展现出广泛且重要的价值。

eBPF的工作原理主要包括加载、编译和执行三个核心步骤。

eBPF程序通常由用户态应用程序通过系统调用加载到内核中。加载过程中,程序代码会被复制到内核空间。

eBPF程序的编译和执行一般由Clang/LLVM编译器完成,生成字节码后载入内核。在此之前,验证器(Verifier)会对程序进行严格的安全检查,以确保其不会破坏内核的稳定性或安全性。若通过检查,程序将通过JIT(Just-In-Time)编译转化为机器特定指令集,以优化执行速度,并在内核中正常运行。

(架构图此处省略)

eBPF程序在内核中运行时,通常会附着在特定的内核钩子(hook)上,以便在特定事件触发时被执行。这些钩子可以包括系统调用、函数入口/出口、网络事件以及Kprobes和Uprobes(用于探测内核或用户函数)。

eBPF Maps机制允许eBPF程序在不同调用之间保持状态,进行数据统计,并与用户空间应用程序共享数据。eBPF Map本质上是一种键值存储,其值通常表示任意数据的二进制块。它们通过`bpf_cmd`系统调用和`BPF_MAP_CREATE`参数创建,并通过文件描述符进行访问和操作(查找、更新、删除)。

总的来说,eBPF通过动态加载、执行和严格检查经过编译的代码,实现了其独特的工作原理。

下面通过一个基于eBPF的性能分析示例,分步说明其应用:

第一步:前置准备:确保内核已启用eBPF功能。通常需在内核配置文件中激活相关选项并重新编译内核。可通过`ls /sys/fs/bpf`和`lsmod | grep bpf`命令检查eBPF支持情况。

第二步:编写eBPF程序:编写eBPF程序以收集内核性能指标。程序可采用C或Python编写,需通过特定接口访问内核数据结构并将收集到的数据存储。以下是一个Python(内嵌C语言)示例代码,用于统计网络数据包数量:

```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数据结构来存储统计结果,并通过捕获`tcp_sendmsg`事件实现实时统计,每秒输出一次数据。这仅是一个基础示例,实际应用可能涉及更复杂的统计与分析。

第三步:运行eBPF程序:使用eBPF编译器(如BCC)将eBPF程序编译成内核可执行格式。BCC工具能够以命令行方式完成编译和加载。

运行上述Python程序的步骤如下:

首先安装`python3-bpfcc`: `sudo apt install python3-bpfcc`。注意:在Python3环境下请勿使用`pip3 install bcc`。

对于Ubuntu 20.10以上版本,若遇编译问题,建议通过源码安装:

```bash apt purge bpfcc-tools libbpfcc python3-bpfcc wget tar xf bcc-src-with-submodule.tar.gz cd bcc/ 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 checkinstall ```

将Python程序保存为`netstat.py`,然后执行:

```bash $ chmod +x ./netstat.py $ sudo ./netstat.py ```

程序运行后,控制台会实时输出网络数据包统计信息。按`Ctrl+C`可终止程序。

再看一个计算TCP发包时间的复杂示例(参考自Github上的相关Issue):

```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语言逻辑并挂载到内核事件上的编程范式。虽然编写这类代码具有一定的挑战性,但幸运的是,BCC工具集中的`tools`目录下已包含大量现成的eBPF程序,可供直接使用。

BCC(BPF Compiler Collection)是一个开源工具集合,专为在Linux系统中使用BPF进行系统级性能分析和监控而设计。它包含众多实用工具,例如:

`bcc-tools`:提供了许多常用的BCC工具。

`bpftrace`:一种高级语言,用于编写和执行BPF程序。

`tcptop`:实时监控和分析TCP流量。

`execsnoop`:监控进程执行情况。

`filetop`:实时监控和分析文件系统流量。

`trace`:跟踪和分析函数调用。

`funccount`:统计函数调用次数。

`opensnoop`:监控文件打开操作。

`pidstat`:监控进程性能。

`profile`:分析系统CPU使用情况。

下图展示了eBPF及其BCC工具集的强大功能,几乎能让内核中的一切运行状况一览无余:

(图片此处省略)

延伸阅读推荐:

Brendan Gregg撰写的《BPF Performance Tools: Linux System and Application Observability》一书,全面涵盖了eBPF的基础知识和实践应用。

由Cilium维护的eBPF官方网站。

Cilium的BPF与XDP参考指南。

BPF官方文档。

BPF设计问答集。

Github上的Awesome eBPF精选列表。

最后,一个有趣的彩蛋是,此文章的初稿部分内容创作曾借助ChatGPT的辅助,通过设定提纲和生成内容,以及进行信息检索来加快撰写进程。

广告位 · 文末横幅