eBPF理解 (一)

目录

一、eBPF简介

二、快速实现BPF程序

三、使用bpf映射


一、eBPF简介

eBPF 是从 BPF (Berkeley Packet Filter) 技术扩展而来的

eBPF 系统启动后就一直运行在那里,它需要事件触发后才会执行。借助kprobe和uprobe,eBPF 程序几乎可以在内核和应用的任意位置进行插桩。

eBPF 的诞生成为内核的一个顶级子系统最典型的就是 iovisor 带来的 BCC、bpftrace 等工具。

图片来源Linux Extended BPF (eBPF) Tracing Tools

BPF开发过程过程

  1. 使用C语言开发eBPF程序
  2. 借助LLVM把eBPF程序编译成BPF字节码
  3. 通过bpf系统调用,把BPF字节码提交给内核
  4. 内核验证并运行BPF字节码,并把相应的状态保存到BPF映射中
  5. 用户通过BPF映射查询BPF字节码的运行状态 

验证过程

  • 只有特权进程才可以执行 bpf 系统调用;
  • BPF 程序不能包含无限循环;
  • BPF 程序不能导致内核崩溃;
  • BPF 程序必须在有限时间内完成。

映射过程,来源,eBPF 程序的运行需要历经编译、加载、验证和内核态执行等过程,而用户态程序则需要借助 BPF 映射来获取内核态 eBPF 程序的运行状态。

安装方式 ubuntu22.04上

sudo apt-get install -y  make clang llvm libelf-dev libbpf-dev bpfcc-tools libbpfcc-dev linux-tools-$(uname -r) linux-headers-$(uname -r)

二、快速实现BPF程序

使用BCC开发eBPF程序,跟踪openat系统调用

1、编辑C程序

int hello_world(void *ctx)
{
        bpf_trace_printk("Hello, World!");
        return 0;
}

新建hello_world.c 文件,

输出Hello, World!打印

bpf_trace_printk时BPF提供的输出,可以用cat /sys/kernel/debug/tracing/trace_pipe查看

2、编辑python程序

from bcc import BPF

b = BPF(src_file="hello_world.c")
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")
b.trace_print()

 python语言,导入BCC模块的BPF

调用BPF加载.c文件

将BPF程序挂在到内核探针kprobe上,event是系统调用openat在内核的实现

打印输出,读取内核中/sys/kernel/debug/tracing/trace_pipe的内容

3、运行eBPF程序

root@root:/# python3 hello.py 
b'CPU:1 [LOST 127 EVENTS]'
b'         systemd-1       [001] d...1  3141.925409: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925410: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925415: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925416: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925417: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925419: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925421: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925423: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925430: bpf_trace_printk: Hello, World!'
b'         systemd-1       [001] d...1  3141.925442: bpf_trace_printk: Hello, World!'

systemd-1 表示进程的名字

[001]表示CPU编号

d...1是选项

3141.9254xx是时间戳

bpf_trace_printk是函数名

Hello,World是打印输出

三、使用bpf映射

BCC定义的库函数和辅助定义

BPF_PERF_OUTPUT函数功能

创建一个 BPF 表,用于通过性能环缓冲区将自定义事件数据推送到用户空间。这是将每个事件的数据推送到用户空间的首选方法。

1、改进的.c文件

//头文件
#include <uapi/linux/openat2.h>
#include <linux/sched.h>

//结构体
struct data_t {
    u32 pid;
    u64 ts;
    char comm[TASK_COMM_LEN];
    char fname[NAME_MAX];
};
//定义性能事件映射
BPF_PERF_OUTPUT(events);

//处理函数
int hello(struct pt_regs *ctx, int dfd, const char __user * filename, struct open_how *how) {
    struct data_t data = {};

    //获取PID和时间
    data.pid = bpf_get_current_pid_tgid();
    data.ts = bpf_ktime_get_ns();

    //获取进程名,把进程名复制到预定义的缓冲区
    if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)  
    {    //读取进程打开的文件名
         bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);  
    }
    //提交性能事件
    events.perf_submit(ctx, &data, sizeof(data));

    return 0;
}

有映射后,bpf_trace_printk就不需要了,用户可以直接从内核态读取数据。

2、改进的python

将C语言写在一起,参考hello_perf_outputpy

from bcc import BPF

# define BPF program
prog = """
#include <linux/sched.h>

// define output data structure in C
struct data_t {
    u32 pid;
    u64 ts;
    char comm[TASK_COMM_LEN];
    char fname[NAME_MAX];

};
BPF_PERF_OUTPUT(events);

int hello(struct pt_regs *ctx,int dfd,const char __user *filename,struct open_how *how) {
    struct data_t data = {};

    data.pid = bpf_get_current_pid_tgid();
    data.ts = bpf_ktime_get_ns();

    if(bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)
    {
        bpf_probe_read(&data.fname,sizeof(data.fname),(void *)filename);
    }


    events.perf_submit(ctx, &data, sizeof(data));

    return 0;
}
"""

# load BPF program
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("openat"), fn_name="hello")

# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))

# process event
start = 0
def print_event(cpu, data, size):
    global start
    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!"))

# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
    b.perf_buffer_poll()

open_perf_buffer定义了名为event的perf映射事件,通过循环调用perf_buffer_poll读取映射的内容。

3、运行eBPF程序

root@root/# python3 hello_perf.py 
TIME(s)            COMM             PID    MESSAGE
0.000000000        b'systemd-oomd'  788    b'/proc/meminfo'
0.250287922        b'systemd-oomd'  788    b'/proc/meminfo'
0.500141038        b'systemd-oomd'  788    b'/proc/meminfo'
0.500233681        b'systemd-oomd'  788    b'/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/memory.pressure'
0.500300281        b'systemd-oomd'  788    b'/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/memory.current'
0.500315337        b'systemd-oomd'  788    b'/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/memory.min'

参考:​​​​​​​

bcc/reference_guide.md at master · iovisor/bcc · GitHub

What is eBPF? An Introduction and Deep Dive into the eBPF Technology

Linux eBPF Tracing Tools (brendangregg.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值