02-eBPF 与系统调用追踪¶
重要性: ⭐⭐⭐⭐ 实用度: ⭐⭐⭐⭐⭐ 学习时间: 2 天 必须掌握: 推荐了解
为什么学这一章¶
eBPF ( Extended Berkeley Packet Filter )是 Linux 内核的一项革命性技术,它允许你在内核中安全地运行自定义代码。学习 eBPF 能让你: - 实现高性能的系统监控和追踪 - 理解现代可观测性工具的底层原理 - 编写自定义的系统调用拦截器 - 掌握 Linux 内核编程的现代方法
学完这一章,你将能够: - ✅ 理解 eBPF 的工作原理和架构 - ✅ 使用 bpftrace 进行系统调用追踪 - ✅ 编写简单的 eBPF 程序 - ✅ 了解 eBPF 在云原生和可观测性领域的应用
📖 核心概念¶
1. 什么是 eBPF¶
┌─────────────────────────────────────────────────────────────────────┐
│ eBPF架构概览 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ eBPF(Extended Berkeley Packet Filter) │
│ ├── 起源:1992年BSD包过滤器(BPF) │
│ ├── 扩展:2014年Linux 3.18引入eBPF │
│ └── 现状:Linux内核最重要的子系统之一 │
│ │
│ 核心特点: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 安全执行 │ │
│ │ • 通过内核验证器(Verifier)检查程序安全性 │ │
│ │ • 禁止无限循环、空指针解引用等危险操作 │ │
│ │ • 保证程序不会崩溃内核 │ │
│ │ │ │
│ │ 2. 高性能 │ │
│ │ • JIT编译:eBPF字节码编译为机器码 │ │
│ │ • 内核态执行:无需用户态/内核态切换 │ │
│ │ • 比传统系统调用追踪快10-100倍 │ │
│ │ │ │
│ │ 3. 灵活性 │ │
│ │ • 动态加载:无需重启内核或修改源码 │ │
│ │ • 事件驱动:响应各种内核事件 │ │
│ │ • 可扩展:支持自定义数据结构(Map) │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ eBPF程序生命周期: │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 编写程序 │───→│ 编译成 │───→│ 内核验证 │───→│ JIT编译 │ │
│ │ (C/Rust) │ │ BPF字节码│ │ 安全检查 │ │ 机器码 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 附加到内核 │ │
│ │ 钩子点执行 │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2. eBPF 的应用场景¶
┌─────────────────────────────────────────────────────────────────────┐
│ eBPF应用场景 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 可观测性(Observability) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 系统调用追踪:strace的现代化替代 │ │
│ │ │ bpftrace -e 'tracepoint:syscalls:sys_enter_openat │ │
│ │ │ { printf("%s opened %s\n", comm, str(args->filename)); }' │ │
│ │ │ │
│ │ • 性能分析:CPU、内存、I/O分析 │ │
│ │ │ bpftrace -e 'kprobe:do_nanosleep │ │
│ │ │ { @start[tid] = nsecs; } │ │
│ │ │ kretprobe:do_nanosleep │ │
│ │ │ /@start[tid]/ │ │
│ │ │ { @latency = hist(nsecs - @start[tid]); }' │ │
│ │ │ │
│ │ • 网络监控:TCP连接、丢包分析 │ │
│ │ │ bpftrace -e 'kprobe:tcp_drop │ │
│ │ │ { printf("TCP drop: %s\n", kstack()); }' │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 网络安全 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 包过滤:XDP(eXpress Data Path)高速包处理 │ │
│ │ │ - 在网卡驱动层处理包,绕过内核网络栈 │ │
│ │ │ - 可达线速(10Gbps+) │ │
│ │ │ │
│ │ • 防火墙:Cilium基于eBPF的容器网络安全 │ │
│ │ │ - 替代iptables,性能提升10倍+ │ │
│ │ │ - 支持L3-L7层策略 │ │
│ │ │ │
│ │ • DDoS防护:Cloudflare使用eBPF进行流量清洗 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 性能优化 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • TCP加速:Google的TCP BBR拥塞控制算法 │ │
│ │ │ - 使用eBPF在内核中实现 │ │
│ │ │ │
│ │ • 负载均衡:Facebook的Katran L4负载均衡器 │ │
│ │ │ - 替换IPVS,性能提升10倍 │ │
│ │ │ │
│ │ • 文件系统加速:eBPF加速ext4/xfs │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 4. 云原生 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 容器安全:Falco运行时安全检测 │ │
│ │ │ - 检测容器中的异常行为 │ │
│ │ │ - 监控文件系统、网络、进程活动 │ │
│ │ │ │
│ │ • 服务网格:Cilium替代Envoy实现服务网格 │ │
│ │ │ - 数据平面使用eBPF │ │
│ │ │ - 延迟降低50%+ │ │
│ │ │ │
│ │ • 无服务器:AWS Lambda使用eBPF进行监控 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3. bpftrace 快速入门¶
bpftrace 是基于 eBPF 的高级追踪语言,类似 awk 的语法,非常适合快速编写追踪脚本。
安装 bpftrace¶
# Ubuntu/Debian
sudo apt-get install bpftrace
# CentOS/RHEL
sudo yum install bpftrace
# macOS(使用Docker或VM)
brew install bpftrace
基础语法¶
┌─────────────────────────────────────────────────────────────────────┐
│ bpftrace语法结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ bpftrace程序结构: │
│ │
│ 探针(Probe) + 过滤条件(Predicate) + 动作(Action) │
│ │
│ 示例: │
│ ```bpftrace │
│ probe /filter/ { action } │
│ ``` │
│ │
│ 探针类型: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 类型 │ 示例 │ 说明 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ kprobe │ kprobe:do_sys_open │ 内核函数入口 │ │
│ │ kretprobe │ kretprobe:do_sys_open │ 内核函数返回 │ │
│ │ tracepoint │ tracepoint:syscalls:sys_enter_openat │ 内核跟踪点 │ │
│ │ uprobe │ uprobe:/bin/bash:readline │ 用户函数入口 │ │
│ │ usdt │ usdt:/usr/bin/python:function__entry │ 用户静态跟踪点│ │
│ │ profile │ profile:hz:99 │ 定时采样 │ │
│ │ interval │ interval:s:1 │ 定时触发 │ │
│ │ software │ software:page-faults │ 内核软件事件 │ │
│ │ hardware │ hardware:cache-misses │ 硬件性能事件 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 内置变量: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 变量 │ 说明 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ pid │ 进程ID │ │
│ │ tid │ 线程ID │ │
│ │ comm │ 进程名 │ │
│ │ nsecs │ 当前时间(纳秒) │ │
│ │ cpu │ CPU编号 │ │
│ │ uid │ 用户ID │ │
│ │ gid │ 组ID │ │
│ │ args │ 函数参数(需要类型转换) │ │
│ │ retval │ 返回值(kretprobe) │ │
│ │ kstack │ 内核调用栈 │ │
│ │ ustack │ 用户调用栈 │ │
│ │ arg0-arg9 │ 函数参数(整数) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 内置函数: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 函数 │ 说明 │ │
│ ├───────────────────────────────────────────────────────────────┤ │
│ │ printf(fmt, ...) │ 格式化输出 │ │
│ │ str(char*) │ 将char*转换为字符串 │ │
│ │ join(char*[]) │ 将字符串数组用空格连接 │ │
│ │ hist(int) │ 生成直方图(2的幂次分桶) │ │
│ │ lhist(int,...) │ 生成线性直方图 │ │
│ │ count() │ 计数 │ │
│ │ sum(int) │ 求和 │ │
│ │ avg(int) │ 平均值 │ │
│ │ min(int) │ 最小值 │ │
│ │ max(int) │ 最大值 │ │
│ │ delete(@x) │ 删除map中的键 │ │
│ │ clear(@x) │ 清空map │ │
│ │ exit() │ 退出程序 │ │
│ │ time(fmt) │ 格式化时间 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
🧪 动手实验¶
实验 1 :系统调用追踪¶
目的:使用 bpftrace 追踪系统调用
步骤:
- 追踪 openat 系统调用:
# 追踪所有进程的文件打开操作
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
printf("%s (PID %d) opened: %s\n", comm, pid, str(args->filename));
}'
# 输出示例:
# bash (PID 1234) opened: /etc/passwd
# ls (PID 5678) opened: /home/user/file.txt
- 统计系统调用频率:
# 统计每个进程的系统调用次数
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter {
@[comm] = count();
}'
# 按Ctrl+C后输出:
# @[bash]: 1234
# @[chrome]: 56789
# @[python]: 345
- 追踪进程创建:
# 追踪fork和exec
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%s (PID %d) executing: %s\n",
comm, pid, str(args->filename));
}'
实验 2 :性能分析¶
目的:分析程序性能瓶颈
步骤:
- CPU 采样分析:
- 函数延迟直方图:
# 测量read系统调用的延迟分布
sudo bpftrace -e 'kprobe:ksys_read {
@start[tid] = nsecs;
}
kretprobe:ksys_read /@start[tid]/ {
@latency = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
# 输出:
# @latency:
# [0, 1] 1234 |@@@@@@@@@@@@@@@@@@@@|
# [2, 4) 567 |@@@@@@@@@|
# [4, 8) 234 |@@@@|
- 文件系统 I/O 分析:
# 追踪VFS读写操作
sudo bpftrace -e 'kprobe:vfs_read, kprobe:vfs_write {
@[func, comm] = sum(arg2); // 统计读写字节数
}'
实验 3 :网络追踪¶
目的:分析网络行为
步骤:
- 追踪 TCP 连接:
# 追踪TCP连接建立
sudo bpftrace -e 'kprobe:tcp_v4_connect {
printf("%s (PID %d) connecting to: ", comm, pid);
}
kprobe:inet_csk_complete_hashdance {
printf("Connection established\n");
}'
- 丢包分析:
# 追踪TCP丢包
sudo bpftrace -e 'kprobe:tcp_drop {
printf("TCP packet dropped by %s\n", kstack());
}'
- 网络延迟:
# 测量网络RTT
sudo bpftrace -e 'kprobe:tcp_sendmsg {
@start[tid] = nsecs;
}
kprobe:tcp_recvmsg /@start[tid]/ {
@rtt = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
实验 4 :编写 eBPF C 程序¶
目的:使用 BCC 编写自定义 eBPF 程序
步骤:
- 安装 BCC:
# Ubuntu
sudo apt-get install bpfcc-tools linux-headers-$(uname -r) # $()命令替换:执行命令并获取输出
# Python库
pip install bcc
- 编写 eBPF 程序:
#!/usr/bin/env python3
# hello_ebpf.py
from bcc import BPF
# eBPF C程序
program = """
#include <linux/sched.h>
// 定义数据结构
struct data_t {
u32 pid;
u32 uid;
char comm[TASK_COMM_LEN];
char message[12];
};
// 定义perf输出缓冲区
BPF_PERF_OUTPUT(events);
int hello(void *ctx) {
struct data_t data = {};
// 获取当前进程信息
data.pid = bpf_get_current_pid_tgid() >> 32;
data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
// 设置消息
bpf_probe_read_kernel_str(&data.message, sizeof(data.message), "Hello eBPF!");
// 提交事件到用户态
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
# 加载eBPF程序
b = BPF(text=program)
# 附加到系统调用
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
# 定义回调函数
def print_event(cpu, data, size):
event = b["events"].event(data)
print(f"PID: {event.pid}, UID: {event.uid}, Comm: {event.comm.decode()}, Message: {event.message.decode()}")
# 循环读取事件
print("Tracing... Press Ctrl+C to exit")
b["events"].open_perf_buffer(print_event)
while True:
try: # try/except捕获异常
b.perf_buffer_poll()
except KeyboardInterrupt:
break
- 运行程序:
sudo python3 hello_ebpf.py
# 在另一个终端执行:
ls
# 输出:
# Tracing... Press Ctrl+C to exit
# PID: 1234, UID: 1000, Comm: bash, Message: Hello eBPF!
💡 核心要点总结¶
eBPF vs 传统工具¶
| 特性 | strace | perf | eBPF/bpftrace |
|---|---|---|---|
| 性能开销 | 高(每次系统调用都中断) | 中 | 低( JIT 编译,内核态执行) |
| 灵活性 | 低(固定功能) | 中 | 高(可编程) |
| 实时性 | 实时 | 采样 | 实时 |
| 学习曲线 | 低 | 中 | 中高 |
| 生产环境 | 不推荐 | 可用 | 推荐 |
eBPF 最佳实践¶
- 性能优先:
- 使用 tracepoint 而非 kprobe (更稳定)
- 避免在内核中做复杂计算
-
使用 per-CPU map 减少锁竞争
-
安全考虑:
- 始终检查 eBPF 程序的返回值
- 避免在生产环境直接运行未测试的程序
-
注意隐私合规(不要追踪敏感数据)
-
调试技巧:
- 使用
bpftrace -d查看调试输出 - 使用
/sys/kernel/debug/tracing/trace_pipe查看内核日志 - 使用
bpftool prog list查看加载的程序
常用工具链¶
┌─────────────────────────────────────────────────────────────────────┐
│ eBPF工具链 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 高级工具(易用) │
│ ├── bpftrace:类awk的追踪语言,适合快速脚本 │
│ ├── bcc:Python/C++库,适合复杂程序 │
│ └── kubectl-trace:Kubernetes中的bpftrace │
│ │
│ 开发工具 │
│ ├── libbpf:C/C++ eBPF加载库 │
│ ├── libbpf-bootstrap:eBPF程序模板 │
│ ├── bpftool:eBPF程序管理工具 │
│ └── LLVM/Clang:编译eBPF程序 │
│ │
│ 可视化平台 │
│ ├── Grafana + Prometheus + eBPF exporter │
│ ├── Pixie:Kubernetes可观测性平台 │
│ └── Groundcover:eBPF应用性能监控 │
│ │
└─────────────────────────────────────────────────────────────────────┘
❓ 常见问题¶
Q1 : eBPF 和内核模块有什么区别?
A :主要区别: - 安全性: eBPF 有验证器保证安全,内核模块可能崩溃系统 - 易用性: eBPF 无需编译内核,可动态加载卸载 - 性能: eBPF 有 JIT 编译,性能接近原生代码 - 灵活性:内核模块功能更强大,但风险更高
Q2 : eBPF 对内核版本有什么要求?
A : - 基础功能: Linux 4.1+ - BPF Map: Linux 4.3+ - BPF 程序类型扩展: Linux 4.9+ - BPF trampoline: Linux 5.5+ - 推荐:使用 Linux 5.10+( LTS 版本)
Q3 : eBPF 程序会影响系统性能吗?
A : - 设计目标: eBPF 追求最小性能开销 - 典型开销:<1% CPU (简单追踪) - 注意事项: - 避免高频事件(如每个包处理) - 减少 map 查找次数 - 使用 per-CPU 数据结构
Q4 :如何学习 eBPF 编程?
A :学习路径: 1. 学习 bpftrace ( 1-2 天) 2. 学习 BCC Python 编程( 1 周) 3. 学习 libbpf C 编程( 2 周) 4. 阅读开源项目( Cilium 、 Falco 等) 5. 实践:编写自己的工具
📚 扩展阅读¶
- 《 Linux Observability with BPF 》 - David Calavera
- BPF 性能工具: brendangregg.com/bpf.html
- bpftrace 参考指南: GitHub.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
- eBPF.io: ebpf.io (社区网站)
- Cilium 文档: docs.cilium.io
🎯 下一步¶
继续学习操作系统交互的后续内容,深入了解进程调度、内存管理、文件系统等核心机制。