eBPF编程之初体验

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大会演讲题目和项目征集。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值