调试Linux性能当我遇到Pixielabs时,我正在做一些有关微服务的跟踪和可观察性的研究。该工具宣传您可以立即对应用程序进行故障排除,而无需在应用程序内部安装任何工具或特殊代码,这对我来说听起来很神奇。因此,自然而然,我想了解更多有关使该技术能够起作用的内容,并且在滚动浏览该站点后,在“ No Instrumentation”部分下使用了这个缩写词eBPF。
在互联网上进行了进一步的挖掘,阅读了设计文件并观看了一些视频之后,可以肯定地说这项技术引起了我的注意,所以我想在上面写一些笔记,希望这篇文章能激发您的更多兴趣。好。
TL; DR版本是该技术使沙盒用户空间应用程序可以在Linux内核本身中运行。在某种程度上,它使内核可编程,从而释放了巨大的可能性。
您可以从内核中选择任何函数,并在每次函数运行时执行该程序。运行“附加”到网络套接字,跟踪点和性能事件的程序可能非常有用。开发人员可以调试内核而无需重新编译。有很多用例,例如:
这样一来,Pixie等工具就可以进行黑盒分析,而无需在我们尝试调试的应用程序中进行任何修补。
BPF最初旨在捕获和过滤符合特定规则的网络数据包,并丢弃所有不需要的数据包。这就是为什么它被称为“伯克利包过滤器”的原因。如果您曾经使用过tcpdump,则可能已经使用过它。如今,eBPF可以用于许多不同的用途,不仅可以用于数据包筛选,还可以出于某些原因而使名称被卡住。
网络监视的体系结构在性能方面大大优于当前的现有数据包捕获技术,这主要是因为过滤是在内核中完成的,而不是在用户空间中复制所有数据包并在其中进行计算。但是随着处理器的进步,其最初的设计并不能很好地适应这一需求。这就是为什么采用扩展BPF(eBPF)设计以利用现代处理能力的原因。
最初,eBPF还可以用作网络数据包过滤解决方案,但是事实证明,在内核内部运行用户空间程序的功能非常强大。这项新设计扩展了BPF虚拟机的功能,即允许它在各种事件上运行,而不仅是数据包,而且还可以执行某些操作,而不仅仅是过滤事物。
eBPF程序只是64位指令的序列。虚拟机的指令集非常有限,并且有两个目标:
所有BPF指令都必须在加载时进行验证,以确保内核的安全。
好的,eBPF代码在内核内部的安全虚拟机中运行。编写这些程序是在某些框架(例如bcc和bpftrace)的帮助下完成的,然后通过bpf系统调用来加载程序字节码。
为了消除在内核中运行用户空间程序时的安全性和稳定性风险,执行了几项检查,以确保我们运行的代码安全并且可以终止,因此我们的计算机不会冻结。
首先,为了甚至能够运行加载eBPF代码的用户程序,它都需要具有root特权,除非启用了非特权eBPF,在这种情况下,该过程可能会加载功能降低的程序。其次,有一个eBPF验证程序,所有eBPF程序都必须经过该程序才能执行。
验证程序会对保护内核的代码进行多项检查。第一步检查确保禁止任何可能冻结内核的无界循环。第二个检查范围更广,它模拟代码的执行过程,以确保代码的所有路径都运行到完成状态,没有超出范围的内存被访问,并且虚拟机的总体状态有效。
验证过程的最后一步涉及限制eBPF程序的功能,可调用的内核功能以及可访问的数据。由于程序类型,验证者知道如何应用这些限制。当我们将程序与某个事件相关联时,将确定程序类型。
验证过程完成后,BIT字节码将被解释或由JIT编译器编译为本地指令,只要内核中发生事件,该指令即可有效运行。
我们可以为内核中几乎所有功能创建和插入内核探针或kprobes(简称为探针),并附加一个eBPF程序,以便它在每次调用该功能时执行。
from bcc import bPF from bcc.utils import printb#define BPF programprog =""" int hello(void * ctx){bpf_trace_printk(" Hello,World!\\ n");返回0; }""" #加载BPF程序b = BPF(text = prog)b .attach_kprobe(event = b .get_syscall_fnname(" clone"),fn_name =" hello")#标头打印(" %-18s%-16s%-6s%s"%(" TIME&#34 ;、" COMM&#34 ;、" PID&#34 ;、&# 34; MESSAGE"))#在1时输出格式:尝试:(task,pid,cpu,flags,ts,msg)= b .trace_fields()除了ValueError:继续,除了KeyboardInterrupt:exit()printb(b&# 34;%-18.9f%-16s%-6d%s"%(ts,task,pid,msg))
eBPF程序是用伪C代码编写的,并以字符串形式放在变量prog中。 bcc框架完成了生成字节码,加载程序和所有其他工作的所有繁重工作,这使编写这些程序变得更加容易。
在此示例中,hello函数调用名为bpf_trace_printk的bpf帮助器函数,该函数输出“ Hello,World!”。跟踪。然后我们加载程序,并为克隆事件附加一个内核探针,基本上是说我们希望在每次调用克隆时都调用hello程序。
此后,脚本等待新的跟踪到达,获取字段并将值打印到屏幕上。
该脚本已成功加载程序,现在我们等待跟踪到达。接下来要做的是尝试调用克隆syscall。我们可以在终端上运行任何命令,例如ls,然后将创建一个子进程。自从我运行了firefox之后,它同时创建了一些子进程,并开始显示跟踪信息:
如我们所见,消息“你好,世界!”与其他跟踪字段一起打印在屏幕上。超酷!
除了附加到内核函数外,我们还可以观察用户空间中调用的函数,例如malloc或strlen。这可以通过另一个Linux内核功能-uprobes来实现。
这是一个有趣的脚本,它通过跟踪strlen()并使用哈希图来存储字符串及其重复计数来计数字符串的频率:https://github.com/iovisor/bcc/blob/master/examples/tracing /strlen_count.py
由于ebpf程序禁止调用任意内核函数,因此内核为我们提供了一些帮助程序函数,例如bpf_trace_printk函数。我们可以使用它们来:
请注意,由于程序可以在不同的上下文中运行,因此每个程序只能使用这些助手的一个子集。这是bpf助手的详尽列表。
要编写更复杂的程序,您需要某种类型的数据存储,而这正是映射的来源。它们是BPF内部系统的重要组成部分,该系统允许程序存储可以从用户空间访问的信息。
BPF支持不同类型的数据存储-当然,根据使用情况,您可以使用哈希映射,数组甚至堆栈或队列。 eBPF为可观察性开辟了各种可能性。 您可以将系统视为黑匣子,而无需真正了解它,然后进行各种有趣的分析:计算最常调用的函数,跟踪网络数据包,进行性能跟踪和调试等。 您可以使用多种工具来观察系统的每个组件:CPU,内存,文件系统,网络,容器,应用程序和mldr 我只是在这个问题上打了水漂,我迫不及待地想了解它,以及一般的Linux性能。 关于这项激动人心的技术,有大量精彩的文章和视频,我在下面为您留下了一些链接。 干杯!