iovisor/bcc项目Python开发教程:从入门到实战
本文将通过一系列实战案例,带您深入了解如何使用Python开发基于iovisor/bcc项目的eBPF工具。我们将从基础概念开始,逐步深入到实际应用开发。
一、eBPF与BCC基础
eBPF(扩展伯克利包过滤器)是Linux内核中的一项革命性技术,它允许用户在不修改内核源代码的情况下运行沙盒程序。BCC(BPF Compiler Collection)则是构建在eBPF之上的工具集,提供了Python、C++等高级语言接口。
1.1 Hello World示例
让我们从一个最简单的"Hello World"程序开始:
from bcc import BPF
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
这个程序有六个关键点需要理解:
text='...'
:定义内联的BPF程序,使用C语言编写kprobe__sys_clone
:内核动态跟踪的快捷方式,自动探测sys_clone系统调用void *ctx
:上下文参数,这里我们不需要使用它bpf_trace_printk()
:内核中的简单打印函数,输出到trace_pipereturn 0
:必需的返回值.trace_print()
:读取trace_pipe并打印输出
1.2 进阶Hello示例
让我们看一个更完整的版本:
from bcc import BPF
from bcc.utils import printb
prog = """
int hello(void *ctx) {
bpf_trace_printk("Hello, World!\\n");
return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
while 1:
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
continue
except KeyboardInterrupt:
exit()
printb(b"%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
这个版本展示了:
- 分离BPF程序定义与加载
- 显式附加kprobe
- 更完善的输出格式化
二、内核事件跟踪实战
2.1 同步调用跟踪
下面是一个跟踪sync系统调用的示例,可以检测短时间内多次调用sync的情况:
from bcc import BPF
b = BPF(text="""
#include <uapi/linux/ptrace.h>
BPF_HASH(last);
int do_trace(struct pt_regs *ctx) {
u64 ts, *tsp, delta, key = 0;
tsp = last.lookup(&key);
if (tsp != NULL) {
delta = bpf_ktime_get_ns() - *tsp;
if (delta < 1000000000) {
bpf_trace_printk("%d\\n", delta / 1000000);
}
last.delete(&key);
}
ts = bpf_ktime_get_ns();
last.update(&key, &ts);
return 0;
}
""")
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace")
print("Tracing for quick sync's... Ctrl-C to end")
[...]
关键点:
BPF_HASH(last)
:创建哈希表类型的BPF映射bpf_ktime_get_ns()
:获取当前纳秒级时间戳- 映射操作:lookup/delete/update
2.2 磁盘I/O分析
下面是一个分析磁盘I/O大小的直方图工具:
from bcc import BPF
b = BPF(text="""
#include <uapi/linux/ptrace.h>
#include <linux/blkdev.h>
BPF_HISTOGRAM(dist);
int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req) {
dist.increment(bpf_log2l(req->__data_len / 1024));
return 0;
}
""")
[...]
b["dist"].print_log2_hist("kbytes")
这个工具展示了:
BPF_HISTOGRAM
:创建直方图类型的BPF映射bpf_log2l()
:计算对数用于直方图分桶print_log2_hist()
:打印对数直方图
三、性能优化技巧
3.1 使用PERF_OUTPUT替代trace_printk
bpf_trace_printk
适合调试但不适合生产环境,更好的方式是使用BPF_PERF_OUTPUT
:
from bcc import BPF
prog = """
#include <linux/sched.h>
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
int hello(struct pt_regs *ctx) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
b = BPF(text=prog)
[...]
b["events"].open_perf_buffer(print_event)
while 1:
b.perf_buffer_poll()
优势包括:
- 更高的性能
- 更灵活的数据结构
- 避免全局trace_pipe的冲突
3.2 内核版本兼容性处理
由于不同内核版本函数可能变化,需要做兼容处理:
if BPF.get_kprobe_functions(b'__blk_account_io_done'):
b.attach_kprobe(event="__blk_account_io_done", fn_name="trace_req_done")
elif BPF.get_kprobe_functions(b'blk_account_io_done'):
b.attach_kprobe(event="blk_account_io_done", fn_name="trace_req_done")
else:
b.attach_kprobe(event="blk_mq_end_request", fn_name="trace_req_done")
四、总结与最佳实践
通过本教程,我们学习了:
- 基本BPF程序结构与加载方式
- 内核函数跟踪与系统调用跟踪
- BPF映射的使用:哈希表、直方图等
- 性能数据采集与输出优化
- 内核版本兼容性处理
开发BCC工具时的最佳实践:
- 优先使用
BPF_PERF_OUTPUT
而非bpf_trace_printk
- 合理选择映射类型:哈希表适合键值对,直方图适合分布统计
- 注意内核版本差异,做好兼容处理
- 复杂数据结构可先在C中定义,再通过Python访问
- 充分利用BCC提供的各种辅助函数简化开发
希望本教程能帮助您快速掌握BCC Python开发的核心要点,为开发更复杂的内核观测工具打下坚实基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考