本文是有关eBPF的系列文章中的第一篇。每个都将在先前的基础上发展,并从概念和上下文过渡到示例和实现。第一篇文章将探讨eBPF的历史,当前状态和未来轨迹。为此,我希望使eBPF的当前状态和功能更加一致。与许多软件项目一样,如果没有塑造它的历史背景,eBPF可能会显得奇怪而痉挛。
本文还引用了将在本系列中撰写的未来文章。对于那些可能引用此文章的人,将在发布这些文章时对其进行更新以引用这些文章。但是,本文的早期版本将可供参考,并且指向它们的链接将发布在此页面上。
介绍
扩展的伯克利数据包过滤器(简称eBPF)很难完整地解释和理解。部分原因是与解决eBPF解决的问题的现有努力有何不同。然而,可以说,最大的原因是它的名字。当某人第一次了解eBPF时,他们可能不知道什么是BPF或对其进行“扩展”意味着什么。“ Berkely”这个世界除了提及美国加利福尼亚州的伯克利市外也无济于事。eBPF名称中唯一可能对初次见过的人有意义的单词是“包过滤器”。这没有说明eBPF可以用于网络过滤以外的许多用途。但是,Linux社区并没有意识到eBPF在命名时会传播多远。
eBPF的核心是驻留在内核中的高效虚拟机。它的初衷是,有效的网络帧过滤,使该虚拟机以及eBPF成为一般处理事件的理想引擎。因此,在撰写本文时,目前有十二种不同类型的eBPF程序,其中许多用于与网络无关的目的。
本文“ eBPF:过去,现在和未来”将介绍BPF(eBPF的前身)的历史,eBPF的当前状态以及未来的发展方向。这样做的目的是让不熟悉eBPF和不熟悉Linux的读者牢牢掌握eBPF的概念和用法,并为它的本质提供一个强有力的上下文。 。
澄清,术语和更正
本文试图尽可能地做到正确,同时又使对eBPF主题一无所知的人也可以接近。设定这样一个崇高的目标迫在眉睫。
- 澄清。不熟悉eBPF和不熟悉Linux的读者可以阅读本文。但是,在花了很多时间研究本文之后,我希望有些细节对我来说似乎很明显,但对于那些不熟悉该主题的人来说却不是。这不是故意的,而是在此内容上花费了40多个小时的结果。如果本文的任何部分需要更多的解释或说明,请向该网站的Github存储库提交问题,说明需要更多信息的地方。
- 条款。 本文主张使用技术上正确的术语,而不是不正确的常用术语。这样的一个例子是伯克利数据包过滤器,尽管它的名字,它过滤的是网络帧,而不是数据包。以下标题为术语的小节列出了由于某种原因可能会被误解的所有术语,以及它们在本文范围内的明确含义。
- 更正。 在撰写本文时,我尝试研究,查找和引用尽可能多的证据来证明此处的内容。话虽这么说,但这是一种代表历史的尝试,在这个领域中,总是要多说些话,并且对一切事物都有多种观点。因此,本文欢迎有支持证据的反馈和更正。可以将反馈和相应的证据作为问题提交到本网站的Github存储库。
条款
- BPF:在FreeBSD中找到的Berkeley数据包过滤器。
- eBPF: Linux中的扩展Berkeley数据包过滤器。
- eBPF映射: eBPF中可用的所有不同类型的数据存储区的总称。即使存在多种类型,eBPF的文档也将所有eBPF数据存储类型都称为“映射”,其中有些不是映射。因此,本文使用术语eBPF-map来明确指定eBPF的数据存储类型,而不是使其完全脱离eBPF自己的文档。
- 网络框架:OSI网络模型的链路层(第2层)的数据单位。示例:以太网。
- 网络数据包:OSI网络模型的网络层(第3层)的数据单位。示例:IPv4
- 网络段:OSI网络模型的传输层(第4层)的数据单位。示例:TCP
BPF和FreeBSD:过去
要了解Linux中的eBPF,最好从其他操作系统开始:FreeBSD。1993年,在美国加利福尼亚州圣地亚哥举行的1993年冬季USENIX会议上,发表了论文“ BSD数据包过滤器-一种用于用户级数据包捕获的新体系结构,McCanne和Jacobson”。它是由当时在劳伦斯伯克利国家实验室工作的史蒂芬·麦卡纳(Steven McCanne)和范雅各布森(Van Jacobson)撰写的。在本文中,McCanne和Jacobson描述了BSD数据包过滤器(简称BPF),包括其在内核中的位置以及作为虚拟机的实现。
BPF与之前的产品(例如CMU / Stanford数据包过滤器(CSPF))的不同之处在于,它使用了经过深思熟虑的内存模型,然后通过内核内部的高效虚拟机将其公开。这样,BPF筛选器可以有效地进行流量筛选,同时仍保持筛选器代码和内核之间的边界。
McCanne和Jacobson在他们的论文中提供了以下图表以帮助形象化。重要的是,BPF在过滤器和用户空间之间使用缓冲区,以避免必须为过滤器匹配的每个数据包在用户空间和内核空间之间进行昂贵的上下文切换。
McCanne和Jacobson所做的最重要的事情也许就是通过以下五个语句来定义虚拟机的设计:
- “它必须与协议无关。不必修改内核即可添加新的协议支持。”
- “必须是一般性的。指令集应足够丰富以处理意外的使用。”
- “数据包数据引用应最小化。”
- “对一条指令进行解码应包含一个C开关语句。”
- “抽象机寄存器[ 1 ]应该驻留在物理寄存器中。”
这种对可扩展性,通用性和性能的强调可能是现代形式的eBPF所包含的功能远远超过BPF最初的实现的原因。但是BPF虚拟机非常简单明了,它“由一个累加器,一个索引寄存器,一个暂存存储器和一个隐式程序计数器组成”。[McCanne和Jacobson]可以从示例程序中看到,该程序在注册码,下面匹配所有IP数据包。每个操作的说明均在其右侧。
ldh [12] // load ethertype field info into register
jeq #ETHERTYPE_IP, L1, L2 // compare register to ethertype for IP
L1: ret #TRUE // if comparison is true, return true
L2: ret #0 // if comparison is false, return 0
仅使用4条虚拟机指令,BPF就能提供非常有用的IP数据包过滤器!为了说明该设计的效率,最初的BPF实现能够拒绝最少184条CPU指令的数据包。这是从bpf_tap调用开始到结束的指令计数,这意味着它可以测量BPF实现和拒绝过滤器所完成的工作。[ 2 ]通过这种方式,BPF使处理器具有了与新型图形计算器相当的功能。通过安全的内核虚拟机处理理论上最大的2.6 Gbps网络流量。[ 3可以通过将其与上下文切换性能进行比较来强调其效率。在SPARCstation 2上,McCanne和Jacobson用来进行这些测量的硬件,进行上下文切换可能需要2840条指令和16000+条指令(相当于71微秒和400+微秒)之间的任意时间,具体取决于争用资源的进程数量和上下文大小的交换。[ 4 ]
最后,在McCanne和Jacobson论文的末尾有两点值得注意。首先,当他们的论文发表时,BPF大约有两年的历史。这是值得注意的,因为它表明BPF的发展是一个渐进的过程,并随着其成功技术的发展而不断发展。其次,在撰写本文时,它提到tcpdump是使用BPF的最广泛使用的程序。[ 5]这意味着tcpdump是当今使用最广泛的网络调试工具之一,已经使用BPF技术至少24年了!我之所以这么说是因为没有其他著作提到BPF技术家族已经使用了多长时间了。当McCanne和Jacobson的论文发表时,BPF并不是一个具有alpha级别实现的好主意。它已经过测试和使用了两年,并且已经在多种工具中找到了应用。
对于那些感兴趣的人,McCanne和Jacobson的论文只有11页,值得一读。可以在此处找到原始文件的扫描结果(/ pdfs / BSD数据包过滤器-一种用于用户级数据包捕获的新体系结构,McCanne和Jacobson,scan.pdf),并且可以找到草稿的PDF渲染[此处](/ pdfs / BSD数据包过滤器-一种用于用户级数据包捕获的新架构,McCanne和Jacobson,draft.pdf)。[ 6 ] [ 7 ]
eBPF和Linux:现在
2014年12月发布Linux内核版本3.18时,它包含扩展BPF(eBPF)的第一个实现。[ 8简而言之,eBPF提供的是内核内虚拟机,如BPF,但有一些关键的改进。第一,eBPF比原始BPF虚拟机更高效,这要归功于eBPF代码的JIT编译。第二,它设计用于内核中的常规事件处理。这使内核开发人员可以将eBPF集成到他们认为合适的用途的内核组件中。第三,它包括eBPF称为“地图”的高效全局数据存储。这种允许状态在事件之间持续存在,因此可以汇总使用,包括统计信息和上下文感知行为。值得注意的是,自eBPF创建以来,数据存储的类型很多,但并非全部都是地图。但是,术语“映射”一直用于指代不同的数据存储类型,本文通过将它们称为“ eBPF映射”做出了妥协。
凭借这些功能,内核开发人员已在各种内核系统中迅速利用了eBPF。在不到两年半的时间里,这些用途已增长到包括网络监视,网络流量操纵和系统监视。因此,eBPF程序可用于交换网络流量,测量磁盘读取的延迟或跟踪CPU缓存未命中。
在继续本文之前,有必要快速澄清一下。Linux社区习惯将eBPF简称为“ BPF”。官方文档通常在远离代码的情况下以“高级”方式将eBPF称为“ eBPF”。但是,许多实现级别的命名约定和文档都将其称为“ BPF”。为了消除任何困惑,在继续之前,截至本文撰写之时,Linux仅具有eBPF,因此对BPF的任何引用实际上都是对eBPF的引用。[ 9 ]与Linux有关的某些文档也使用“ cBPF”,这是“经典BPF”的缩写,引用BPF,使两者之间的区别更加清晰。
了解eBPF当前实施重点的最快方法是逐步了解使用eBPF的要求。eBPF程序所经历的过程可以分为三个不同的部分。
- 创建eBPF程序作为字节码。 当前,创建eBPF程序的标准方法是将它们编写为C代码,然后让LLVM将其编译为驻留在ELF文件中的eBPF字节代码。但是,eBPF虚拟机的设计已被很好地记录下来,其代码以及围绕它的许多工具都是开源的。因此,对于发起,完全有可能手工制作eBPF程序的注册码,或者编写eBPF的自定义编译器。由于eBPF的设计极为简单,因此必须内联eBPF程序的所有功能(入口点功能除外),以便LLVM进行编译。本系列文章的第三部分(尚未发布)将对此进行更详细的介绍。对于那些同时寻求更多信息的人,请参阅本文的“ 进一步阅读”部分。
- 将程序加载到内核并创建必要的eBPF映射。 这是使用bpfLinux中的syscall 完成的。此syscall允许加载字节码以及正在加载的eBPF程序类型的声明。在撰写本文时,eBPF具有用作套接字过滤器,kprobe处理程序,流量控制调度程序,流量控制操作,跟踪点处理程序,eXpress数据路径(XDP),性能监视器,cgroup限制和轻量级隧道的程序类型。syscall也用于初始化eBPF映射。本系列的第二部分通过“ eBPF第2部分:Syscall和映射类型”一文解释了此syscall的选项和实现细节。第三批致力于通过使用当前的Linux工具,主要是tc和ip从iproute2开始,出于此目的,下面的内容将详细介绍该操作的底层操作。对于那些同时寻求更多信息的人,请参阅本文的“ 进一步阅读”部分。
- 将加载的程序附加到系统。 由于eBPF的不同用法用于Linux内核中的不同系统,因此每种eBPF程序类型都有不同的过程来附加到其相应的系统。附加程序后,程序将变为活动状态,并开始过滤,分析或捕获信息,具体取决于创建该程序的目的。用户空间程序可以从此处管理正在运行的eBPF程序,包括从其eBPF映射中读取状态;如果以这种方式构造程序,则可以操纵eBPF映射以更改程序的行为。对于那些同时寻求更多信息的人,请参阅本文的“ 进一步阅读”部分。
这三个步骤在概念上很简单,但在使用上却非常细微。以后的文章将对此进行更详细的介绍,当它们完成时,我将在本文中链接到它们。同时,要知道当前的工具确实在使用eBPF方面提供了很多帮助,即使可能与使用它们的经验背道而驰。像Linux中的许多东西一样,在没有其他工具帮助的情况下,从内核内部使用eBPF可能比从内核外部使用eBPF更好。重要的是要记住,eBPF的通用性既导致了其惊人的灵活性,又使其使用变得复杂。对于那些希望围绕eBPF构建新功能的人,当前的Linux内核提供了更多的框架,而不是开箱即用的解决方案。[ 10 ]
为了快速回顾一下,eBPF自2014年以来一直位于Linux内核中,但已经在内核中用于多种不同用途,以进行有效的事件处理。借助eBPF映射,用eBPF编写的程序可以维护状态,从而跨事件聚合信息,并具有动态行为。eBPF的使用由于其极简的实现方式和轻快的速度性能而继续得到扩展。现在介绍eBPF的未来期望。
智能网卡和内核空间程序:未来
在短短几年内,eBPF已集成到许多Linux内核组件中。这使得eBPF的未来前景既有趣,但方向也不明确。这是因为确实存在两个未来的问题:eBPF的使用的未来和eBPF作为技术的未来。
eBPF的使用的近期可能是最确定的。在Linux内核中使用eBPF进行安全,高效,事件处理的趋势很可能会继续。由于eBPF是由运行它的简单虚拟机定义的,因此eBPF也有可能在Linux内核以外的其他地方使用。在撰写本文时,最有趣的示例是使用智能NIC。
智能NIC是网卡,它允许将网络流量的处理在不同程度上转移给NIC本身。这个想法自2000年代初就出现了,当时一些NIC开始支持卸载校验和和分段,但是直到最近才用于部分或全部数据平面卸载。这种新型的智能NIC有多种名称,包括“智能服务器适配器”,但通常具有可编程功能和用于存储状态的大量内存。Netronome的Agilio CX SmartNIC系列产品就是一个例子,该产品目前具有10Gbps至40Gbps端口,ARM11处理器,2GB DDR3 RAM和一百多个专用处理内核。[ 11 ]
由于其中的大量处理能力,最近的智能NIC已成为eBPF的目标。这样,它们构成了多种用途的主要方法,包括减轻DoS攻击并提供动态网络路由,交换,负载平衡等。在2016年10月的NetDev 1.2大会上,Netronome的Jakub Kicinski和Nic Viljoen作了演讲。标题为“将eBPF / XDP硬件卸载到SmartNIC”。Nic Viljoen在其中指出了一些非常粗糙的早期基准,即在Netronome SmartNIC上为每个FPC每秒实现300万个数据包的基准。正如Viljoen继续指出的那样,每个SmartNIC都有72至120个此类FPC,给出了一个假设的(尽管可能不现实的)最大eBPF吞吐量4.3 Tbps![ 12 ]
最后,eBPF作为技术将有未来。由于eBPF的限制和简单实现,它提供了一种高度可移植且高效的方式来处理事件。然而,更重要的是,eBPF要求改变解决问题的方式。它删除了对象和有状态代码,而是仅选择功能和有效的数据结构来存储状态。这种范例极大地缩小了程序设计的可能性,但是这样做却使其几乎可以与任何程序设计方法兼容。因此,eBPF可以同步,异步,并行,分布式(取决于与数据存储的协调需求)以及所有其他方式的程序设计来使用。考虑到这一事实,我认为eBPF的更合适的名称是功能虚拟机,简称FVM。
eBPF可能会为将来的软件带来的最大技术变化是由于eBPF容易被人们遗忘:它将字节码JIT编译为机器代码以执行内核空间!因此,eBPF避免了内核由于硬件隔离而给用户空间程序带来的巨额成本,即25%到33%之间的性能下降。[ 13 ]这意味着用户程序可能会运行通过在内核空间虚拟机上运行, 速度提高了50%![ 14 ]实际上,这不是一个新想法。2014年,加里·伯恩哈特(Gary Bernhardt)在PyCon上做了题为“ JavaScript的诞生与死亡”的有趣而有趣的演讲。。Bernhardt在其中引用了相同的硬件隔离成本统计信息,他解释说,在虚拟的将来,所有软件中的大多数都如何在内核Javascipt虚拟机上运行。在这样的未来中,软件可移植性将不再是问题,因为软件不是编译为硬件体系结构,而是编译为Javascript。他甚至继续说,文档对象模型(DOM)的实现(已保存在浏览器中的数据存储,用于保存呈现网页的状态)也已移入内核,该内核现在在其中运行窗口系统。这在概念上与eBPF映射非常相似,eBPF映射在理论上可以在将来用于此类情况。虽然有些轻松,不可否认的是,伯恩哈特的演讲是建立在可靠的事实之上的,并且对于未来由内核空间虚拟机而非硬件完成计算机程序隔离的未来可能是正确的。我们只需要等待,看看eBPF是否成为这一改变的支持者。