eBPF学习计划可以看这里

该篇为入门文章翻译系列第二篇,第一篇看这里

原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/

BCC是什么

在本系列的前一篇文章中,我讨论了如何使用eBPF安全地运行内核内用户空间提供的代码。然而,对于新手来说,eBPF最大的挑战之一是编写程序需要编译并从内核源代码链接到eBPF库。内核开发人员可能总是可以获得内核源代码的副本,但是对于在生产环境机器或客户机器上工作的工程师来说,情况就不一样了。解决这个限制是创建BPF编译器集合的原因之一。该项目包括用于编写、编译和加载eBPF程序的工具链,以及用于调试和诊断性能问题的示例程序和久经考验的工具。

自2015年4月发布以来,许多开发人员都研究过BCC项目,其中113位贡献者已经一起创建并提供了令人印象深刻的工具集,包括100多个示例和随时可用的跟踪工具。例如,使用用户静态定义跟踪(USDT)探测的脚本(一种来自DTrace的在用户空间代码中放置跟踪点的机制),用于跟踪垃圾收集事件方法调用和系统调用,以及高级语言中的线程创建和销毁。许多流行的应用程序,特别是数据库,也有USDT探测,可以通过配置开关(—-enable-dtrace)启用它。这些探测,顾名思义,会在编译时,被静态地插入到用户应用程序中。在不久的将来,我将专门写一篇关于USDT探测的LWN文章。

项目文档展示了如何使用现有的脚本和工具进行全面的性能调查,而不需要编写一行代码,BCC项目库中提供了一个方便上手的教程另一个有用指南是由Brendan Gregg撰写的,他对BCC和工具补丁的贡献数量排名第二(在撰写本文时,Sasha Goldshtein的贡献数量排名第一)。

在BCC中可以使用Python和Lua语言的作为入口进行编程。使用这些高级语言,可以编写短小但富有表现力的程序,同时具备C语言所缺少的全部数据操控的优势。例如,开发人员可以将eBPF map类比为Python字典,并可以直接访问映射内容,这是通过使用BPF帮助函数,它在内部实现这个功能。这有助于降低使用eBPF的潜在开发人员的门槛,因为他们可以使用处理数据惯用的标准模式。

BCC调用LLVM Clang编译器,这个编译器具有BPF后端,可以将C代码转换成eBPF字节码。然后,BCC负责使用bpf()系统调用函数,将eBPF字节码加载到内核中。如果加载失败,例如内核验证器检查失败,则BCC提供有关加载失败原因的提示,如,“提示:如果在没有首先检查指针是否为空的情况下,从map查找中取消引用指针值,可能就会出现The 'map_value_or_null'”。这是创建BCC的另一个动机——因为很难写出明显正确的BPF程序;当你犯了错误时,BCC会通知你。

一个非常快速的”Hello, World“示例

为了演示如何快速地开始使用BCC,下面是来自BCC项目的“Hello, World!”示例程序(译者注:必须使用root权限执行)。每次运行系统函数clone()时,它都会打印到跟踪缓冲区中。我稍微修改了一下格式,以便于阅读。

#!/usr/bin/env python
from bcc import BPF

program='''
int kprobe__sys_clone(void *ctx)
{
    bpf_trace_printk("Hello, World!\n");
	return 0;
}
'''

整个eBPF程序包含在program变量中。它是运行内核里的eBPF虚拟机上的代码。函数名“kprobe__sys_clone()”的格式很重要:kprobe__前缀表示BCC工具链将一个kprobe附加到它后面的内核符号上。在这里,是sys_clone()这个符号。当调用sys_clone()时,这个kprobe会被触发,然后运行eBPF程序,bpf_trace_printk()会打印“Hello, World!”到内核的跟踪缓冲区中。

以前比较繁琐的任务是,将程序编译为eBPF字节码,并将其加载到内核。现在完全只需通过实例化一个新的BPF对象就可以处理。所有低层次的工作都是在幕后完成的,就Python bindings和BCC的libbpf库中。

函数BPF.trace_print()对内核的跟踪缓冲区文件执行阻塞读取,并将内容打印到标准输出中。这是输出内容:

    gnome-terminal--3210  [003] d..2 19252.369014: 0x00000001: Hello, World!
    gnome-terminal--3210  [003] d..2 19252.369080: 0x00000001: Hello, World!
    pool-21543 [001] d..2 19252.382317: 0x00000001: Hello, World!
    bash-21545 [002] d..2 19252.385535: 0x00000001: Hello, World!
    bash-21546 [003] d..2 19252.385752: 0x00000001: Hello, World!
    bash-21545 [002] d..2 19252.386883: 0x00000001: Hello, World!

输出内容格式说明如下:

  • kprobe触发时正在运行的应用程序名称
  • 这个应用程序的PID
  • 运行在哪个CPU核心上(在[]里面)
  • 各种进程上下文标志
  • 时间戳

最后一个字段就是我们传递给bpf_trace_printk()的“Hello, World!”字符串。倒数第二个字段包含0x00000001这个地址。通常情况下,当内核代码写入跟踪缓冲区时,系统指令trace_printk()被调用后,这个指令的指针地址将打印在该字段中。不幸的是,并没有为bpf_trace_printk()这个系统函数实现这个情况,所以总是使用硬编码的地址0x00000001

译者注: 为了更方便大家动手操作,提供了vagrant虚拟机环境,已安装bcc工具集合。

下载方式如下所示:链接: https://pan.baidu.com/s/11dsEU6Yk6KGDGNor-fbsgQ 提取码: qvhc。

使用方式如下所示:

# download the box locally
> vagrant init [ur-box-name] [the-path-where-ur-box-is-located]
> vagrant up
# BCC base dir is `/usr/share/bcc`
# BCC tool collection is in `/usr/share/bcc/tools`
# You can find the examples in `/usr/share/bcc/examples`

为大家录制了 Hello World 操作视频。

更多示例

argdist.py这个文件将探针(uprobe、kprobe、tracepoint或USDT)插入到给定的函数中,该函数可以位于内核中,也可以位于用户空间代码中。当探针被触发时,argdist.py会打印函数的参数值,以计数器或直方图的形式显示。它会一直运行,直到被用户中断。例如,下面的命令输出调用irq_handler_entry()的次数,以及引发中断的次数。

    $ tools/argdist.py -C 't:irq:irq_handler_entry():int:args->irq'
    [14:14:24]
    t:irq:irq_handler_entry():int:args->irq
    COUNT      EVENT
    12         args->irq = 45
    16         args->irq = 53
    52         args->irq = 48
    [14:14:25]
    t:irq:irq_handler_entry():int:args->irq
    COUNT      EVENT
    1          args->irq = 49
    5          args->irq = 53
    24         args->irq = 45

由于histogram选项(-H)使用桶将多个中断分组在一起,所以在这种情况下,它不如count选项(-C)有用。但是,直方图输出很有帮助的一个场景,是使用btrfsdist.py工具,该工具将Btrfs的读、写、打开和fsync操作的延迟,汇总到power-of-two桶中。

    $ tools/btrfsdist.py
    Tracing btrfs operation latency... Hit Ctrl-C to end.
    ^C

    operation = 'read'
     usecs               : count     distribution
         0 -> 1          : 775      |****************************************|
         2 -> 3          : 60       |***                                     |
         4 -> 7          : 20       |*                                       |
         8 -> 15         : 3        |                                        |
        16 -> 31         : 3        |                                        |
        32 -> 63         : 0        |                                        |
        64 -> 127        : 0        |                                        |
       128 -> 255        : 1        |                                        |
       256 -> 511        : 19       |                                        |
       512 -> 1023       : 12       |                                        |

    operation = 'write'
     usecs               : count     distribution
         0 -> 1          : 0        |                                        |
         2 -> 3          : 2        |**********                              |
         4 -> 7          : 8        |****************************************|
         8 -> 15         : 1        |*****                                   |
        16 -> 31         : 4        |********************                    |
        32 -> 63         : 4        |********************                    |

    operation = 'open'
     usecs               : count     distribution
         0 -> 1          : 636      |****************************************|
         2 -> 3          : 22       |*                                       |
         4 -> 7          : 16       |*                                       |
         8 -> 15         : 2        |                                        |
        16 -> 31         : 1        |                                        |

    operation = 'fsync'
     usecs               : count     distribution
         0 -> 1          : 0        |                                        |
         2 -> 3          : 0        |                                        |
         4 -> 7          : 0        |                                        |
         8 -> 15         : 0        |                                        |
        16 -> 31         : 0        |                                        |
        32 -> 63         : 0        |                                        |
        64 -> 127        : 0        |                                        |
       128 -> 255        : 0        |                                        |
       256 -> 511        : 0        |                                        |
       512 -> 1023       : 0        |                                        |
      1024 -> 2047       : 0        |                                        |
      2048 -> 4095       : 0        |                                        |
      4096 -> 8191       : 1        |****************************************|

未来还会有更多

这是对bbc的简短介绍。在下一篇文章中,我们将探索一些更复杂的主题,比如如何访问eBPF数据结构,如何配置eBPF程序的编译方式,以及如何调试程序,所有这些都使用Python语言作为入口。

1 对 “eBPF文章翻译(2)——BCC介绍(附实验环境)”的想法;

发表评论

邮箱地址不会被公开。 必填项已用*标注