eBPF编程从BCC开始
1.BCC简介
BCC是一个Python库,简化了eBPF应用的开发过程,并收集了大量性能分析相关的eBPF应用。BCC为BPF开发提供了不同的前端支持,包括Python和Lua,实现了map创建、代码编译、解析、注入等操作,使开发人员只需聚焦于用C语言开发要注入的内核代码。 BCC工具集大部分工具需要Linux Kernel 4.1以上版本支持,完整工具支持需要Linux Kernel 4.15以上版本支持。
GitHub - iovisor/bcc: BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and moreBCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more - GitHub - iovisor/bcc: BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and morehttps://github.com/iovisor/bcc
2.BCC安装
(1) 内核配置
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
# [optional, for tc filters]
CONFIG_NET_CLS_BPF=m
# [optional, for tc actions]
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
# [for Linux kernel versions 4.1 through 4.6]
CONFIG_HAVE_BPF_JIT=y
# [for Linux kernel versions 4.7 and later]
CONFIG_HAVE_EBPF_JIT=y
# [optional, for kprobes]
CONFIG_BPF_EVENTS=y
# Need kernel headers through /sys/kernel/kheaders.tar.xz
CONFIG_IKHEADERS=y
CONFIG_NET_SCH_SFQ=m
CONFIG_NET_ACT_POLICE=m
CONFIG_NET_ACT_GACT=m
CONFIG_DUMMY=m
CONFIG_VXLAN=m
更改之后,重启服务器
(2)安装依赖项
# For Focal (20.04.1 LTS)
sudo apt install -y bison build-essential cmake flex git libedit-dev \
libllvm12 llvm-12-dev libclang-12-dev python zlib1g-dev libelf-dev libfl-dev python3-distutils
(3)源码安装BCC
git clone https://github.com/iovisor/bcc.git
mkdir bcc/build; cd bcc/build
cmake ..
make
sudo make install
cmake -DPYTHON_CMD=python3 .. # build python3 binding
pushd src/python/
make
sudo make install
popd
eBPF编程之初体验
1.跑跑hello world
#!/usr/bin/python
from bcc import BPF
"""
test="…"包含的是C语言编写的BPF程序
kprobe__sys_clone()对应内核kprobes的动态跟踪,也就是sys_clone()接口;
void *ctx这里没有用到参数,直接设置为void *类型,ctx 保存BPF执行时的上下文
bpf_trace_printk()是一个通用的打印函数,会输出到trace_pipe中(/sys/kernel/debug/tracing/trace_pipe文件)然后trace_print()会读取上面的输出
"""
BPF(text="""
int kprobe__sys_clone(void *ctx){
bpf_trace_printk("hello,world!\\n");
return 0;
}
""").trace_print() # 从管道文件中获取数据,打印在屏幕上
-
bash-xxx
:是进程的名字-PID -
[003]
: 表示CPU编号 -
……
:表示一系列选项 -
90.038099
:表示时间戳 -
hello,world!
: 表示输出的消息
#!/usr/bin/python
from bcc import BPF
# 1) eBPF程序
program = """
int hello_world(void *ctx)
{
bpf_trace_printk("Hello, World!");
return 0;
}
"""
# 2) 加载eBPF程序
b = BPF(text=program)
# 3)
# 为内核调用创建一个kprobe, 它将执行上述定义的hello函数。
# get_syscall_fnname()返回syscall对应的内核函数名,并使用正确的前缀与syscall名称连接。
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
# 4)/sys/kernel/debug/tracing/trace_pipe
b.trace_print()
格式化输出
from bcc import BPF
# 定义的bpf程序
prog = """
int hello(void *ctx) {
bpf_trace_printk("Hello, World!\\n");
return 0;
}
"""
# 加载bpf程序
b = BPF(text=prog)
# 为内核调用创建一个kprobe, 它将执行上述定义的hello函数。
# 返回syscall对应的内核函数名,并使用正确的前缀与syscall名称连接。
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:
# 从 trace_pipe 返回一组固定的字段
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
continue
print("%-18.9f %-16s %-6d %-6d %s" % (ts, task, cpu, pid, msg))
2.跟踪sync命令
"""
Linux sync命令用于数据同步,sync命令是在关闭Linux系统时使用的。
Linux 系统中欲写入硬盘的资料有的时候为了效率起见,会写到 filesystem buffer 中,
这个 buffer 是一块记忆体空间,如果欲写入硬盘的资料存于此 buffer 中,
而系统又突然断电的话,那么资料就会流失了,sync 指令会将存于 buffer 中的资料强制写入硬盘中。
"""
# 即使在python2.X,使用print就得像python3.X那样加括号使用
from __future__ import print_function
from bcc import BPF
prog = """
#include <uapi/linux/ptrace.h>
// 创建一个BPF的hash,称为last。默认的键和值为u64
BPF_HASH(last);
int do_trace(struct pt_regs *ctx) {
u64 ts, *tsp, delta, key = 0;
// 查找散列中的键,并返回指向其值(如果存在)的指针,否则为 NULL
tsp = last.lookup(&key);
// 尝试读取存储中的时间戳
if (tsp != NULL) {
// 获取时间,以纳秒为单位
delta = bpf_ktime_get_ns() - *tsp;
if (delta < 1000000000) { //当两次sync 相隔的时间小于一秒时,就打印时间。
bpf_trace_printk("%d\\n", delta / 1000000);
}
last.delete(&key);
}
ts = bpf_ktime_get_ns();
last.update(&key, &ts);
return 0;
}
"""
b = BPF(text=prog)
# 挂在bpf程序。
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace")
print("Tracing for quick sync's... Ctrl-C to end")
# 格式化输出
start = 0
while 1:
(task, pid, cpu, flags, ts, ms) = b.trace_fields()
if start == 0:
start = ts
ts = ts - start
print("At time %.2f s: multiple syncs detected, last %s ms ago" % (ts, ms))
统计sync调用的次数
#!/usr/bin/python3
from bcc import BPF
prog = '''
#include <uapi/linux/ptrace.h>
// 创建一个array用于计数
BPF_ARRAY(counts, u64, 1);
int do_sync(struct pt_regs *ctx) {
u64 *now = 0;
int index = 0;
counts.increment(index); //自增1
now = counts.lookup(&index);
if (now != NULL) {
bpf_trace_printk("%d\\n", *now);
}
return 0;
}
'''
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_sync")
while(1):
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
continue
print("At time %.2f s: count sync is %s\n" % (ts, msg))
3.perf事件
#!/usr/bin/python3
from multiprocessing import Event
from bcc import BPF
prog = '''
#include <linux/sched.h>
// 自定义输出类型
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
};
// 创建perf输出通道,名为event
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.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
start = 0
# 该函数将处理从事件流中读取事件
def print_event(cpu, data, size):
global start
# 将事件作为 Python 对象获取,从 C 声明中自动生成。
event = b["events"].event(data)
if start == 0:
start = event.ts
time_s = (float(event.ts - start)) / 1000000000
print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid, "Hello, perf_output!"))
# 设置回调函数
b["events"].open_perf_buffer(print_event)
while 1:
# 阻塞等待事件
b.perf_buffer_poll()
eBPF编程之学习路
1.书籍
《Linux内核观测技术BPF》
《BPF之巅_洞悉Linux系统和应用性能》
2.BCC/docs 和 BCC/examples
3.开源社区
Linux内核之旅
龙蜥社区
eBPF官方:eBPF - Introduction, Tutorials & Community Resources
BPF官方文档:BPF Documentation — The Linux Kernel documentation
首届中国eBPF大会:ebpfConference: 首届中国eBPF大会演讲题目和项目征集。