bcc 开发练习
- bpf_txt:BPF C 程序,包含一个函数hello_world_printk。
- void *ctx: ctx有参数(如struct pt_regs *ctx),但由于我们在本例中没有使用它们,所以我们可以将其转换为void *
- bpf_trace_printk():一个简单的内核工具,用于printf()到公共trace_pipe ( /sys/kernel/debug/tracing/trace_pipe )。
- bpf_ctx = BPF(text=bpf_txt):这本质上加载 BPF C 程序并通过编译器、验证器等运行它,并返回我们用来启用事件的 BPF 对象。
- Attach_kprobe(event='...', fn_name='...') :使用函数入口的内核动态跟踪来检测由event的值指定的内核函数,并附加我们的 C 定义的函数(由fn_name的值,在执行命中该事件时调用。
- trace_print(fmt='...'):一个 BCC 例程,读取trace_pipe并以指定格式打印输出(可选)(与bpf_trace_printk一起使用)。
默认情况下(即仅trace_print()),从trace_pipe读取的每个数据都带有以下字段: {1}:进程 ID (PID) {2}:CPU# {3}:标志 {4}:Unix 时间戳 {5}、{6}、{7}:可选字段(如果使用这些字段)
在我们的例子中,我们只想打印消息“Hello World!”,因此我们只指定第 5 个 ( {5} ) 字段,其中包含我们的消息
跟踪打印
- while 1:我们在这里使用无限 while 循环(就像大多数 BPF 程序一样),这样我们就可以连续轮询trace_pipe(或任何其他输出流)并检查另一端是否有数据要读取。
使用try:和except:进行更安全的处理
- BPF_PERF_OUTPUT(events):创建一个 BPF 表,用于通过 perf 环形缓冲区将自定义事件数据推送到用户空间。
- 将每个事件数据推送到用户空间的首选方法
- 在我们的示例中,输出表名为events,自定义事件数据(在struct data_t data中)通过以下方式推送到用户空间。events.perf_submit()
- BPF_PERF_输出
- struct data_t:定义自定义 C 数据结构以将数据(通过 perf 的环形缓冲区)从内核传递到用户空间.在我们的示例中,我们发送的唯一数据是“Hello World!” string,所以我们定义一个长度>=字符串实际长度的char数组
-
__builtin_memcpy() : memcpy()的内置版本。
回想一下,BPF C 代码中不允许有循环,因此不允许memcpy()
-
events.perf_submit() :使用BPF_PERF_OUTPUT表将自定义事件数据提交到用户空间的方法。
-
open_perf_buffer
bpf_ctx["events"].open_perf_buffer(handle_event):将 Python handle_event函数与BPF_PERF_OUTPUT(events)输出流关联。
-
perf_buffer_poll():轮询任何打开的性能缓冲区以获取新数据。
-
handle_event(cpu, data, size):处理从事件流中读取事件的Python函数。
- 函数参数cpu、data、size是必需的参数
- bpf_ctx["events"].event(data)将事件数据作为 Python 对象抓取,该对象是从 C 数据结构struct data_t自动生成的
- Python事件对象的字段与BPF C程序中定义的完全相同(例如output.str)
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
TIME(s) COMM PID MESSAGE
19701.696061000 postgres 880 Hello, World!
19739.556299000 systemd-logind 680 Hello, World!
19739.557980000 systemd-udevd 325 Hello, World!
19745.895216000 systemd-logind 680 Hello, World!
19745.896988000 systemd-udevd 325 Hello, World!
直方图编译BPF出错
控制台sync;sync;sync 触发
- bpf_ktime_get_ns():返回以纳秒为单位的时间。
- BPF_HASH(last):创建一个BPF映射对象,它是一个散列(关联数组),称为“last”。我们没有指定任何进一步的参数,因此它默认为 u64 的键和值类型。
- key = 0:我们只会在此哈希中存储一个键/值对,其中键被硬连接为零。
- last.lookup(&key):在哈希中查找键,如果存在则返回指向其值的指针,否则返回NULL。我们将密钥作为地址传递给指针。
- if (tsp != NULL) {:验证程序要求必须检查从映射查找派生的指针值是否为空值,然后才能取消引用和使用它们。
- last.delete(&key):从哈希中删除密钥。由于4.8.10中的内核错误.update()(已在 4.8.10 中修复),目前需要这样做。
- last.update(&key, &ts):将第二个参数中的值与键关联,覆盖任何先前的值。这记录了时间戳。
不能使用*curct++,可能是因为内核数据的原因,只能通过count.update更新;
TODO
TODO