🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
epbf part1 过去,现在与未来 这是bpf系列的第一篇文章,每个都将基于先前的概念与关系去介绍例子与实现。 第一篇文章将会探索bpf的历史,目前状态,以及未来轨迹。这样做的目的是以便于使得bpf跟当前的 状态与功能更相符,就像许多的软件工程一样,如果没有背景,看起来可能会很奇怪。 直接正文: 扩展的BPF,简短说ebpf,很难完整的解释与理解。很大程度上是因为它与ebpf先前的用途有很大的不同, 然而,可以说,最大的问题在于它的名字,当有人第一次学习bpf时并不会知道bpf是什麽以及什么又是扩展(extend) ,伯克利也不利于区分出美国加利福利亚的伯克利,唯一有意义的单词是“封包过滤”,这也不会表明bpf可以用来做的不仅仅是网络过滤, 不过,社区在创造ebpf时也没预料到会传播多远。 在ebpf的核心,是位于内核的高效率虚拟机,原本是用于高效的网络帧过滤, 使这个虚拟机,从而使bpf成为处理内核事件的理想引擎。基于这一事实,目前有十二种不同类型的bpf程序。 在写这篇文章时,他们当中的许多并不是跟网络有关。 这篇文章,将会过一遍bpf的历史(以及继承者ebpf),ebpf的当前状态,以及将来的用途, 这篇文章不需要读者具备bpf知识,只要中等的linux熟悉度,便可以对于ebpf从概念到使用以及如何演进获得一个坚实的理解。 术语: bpf:伯克利封包过滤,freeBSD创建。 ebpf:扩展的伯克利封包过滤,linux创建。 ebpf-map:通用术语用来描述各种不同类型的bpf数据存储。bpf文档描述所有的ebpf数据存储类型为maps即使有个种类型,其中一些并不是map。 因此,本文使用术语ebpf-map来明确指定ebpf的数据存储类型。同时不会使其与epbf自己的文档完全脱节。 network-Frame(网络帧):osi模型中链路层的数据单元,例如Ethernete。 network-Frame(网络帧):osi模型中链路层的数据单元,例如Ethernete。 network-package:osi网络层,例如ipv4. network-Segment: osi中的传输层,例如tcp。 bpf与freeBSD:过去 想要在linux理解bpf最好是从一个不同的操作系统开始,1993年的论文“BSD封包过滤-一种新的架构用于捕获用户空间封包, McCanne & Jacobson” 在1993年美国加利福尼亚圣地亚哥举行的冬季usenix会议上发表,由Steven McCanne 与 Van Jacobson共同编写, 那时候,两位都效力于劳伦斯伯克利国家实验室。在论文中,McCanne and Jacobson描述了BSD封包过滤,或者叫BPF,包括他在内核中的位置,以及作为虚拟机的实现。 是什么让bpf跟他的前辈们如此不同呢,比如CMU/stanford packet filter(cspf) 是使用经过考量的内存模型,然后通过内核中高效的虚拟机将其公开。通过这样,bpf过滤器可以做高效流量过滤同时在过滤用的代码与内核之间保持一个边界。 在他们的文章中,McCanne and Jacobson提供了如下的图标去帮助理解,主要的是,BPF在过滤器与用户空间采用了一个缓冲区去避免了匹配到的包在用户空间与内核空间之间 昂贵的上下文切换。 McCanne and Jacobson做的最主要的工作通过以下5点从新定义并设计了虚拟机: 1:必须是协议独立的,当添加新协议时,内核不应该被修改。 2:必须是通用的,指令集应当足够丰富以便于处理没有预料的使用。 3:尽量减少数据包的数据引用。 4:解码语句应当由单个C switch语句完成。 5:抽象的寄存器应当保留在物理内存中。 这强调了可扩展性,通用性,对于性能的追求,可能是ebpf超过其他bpf实现的原因。目前bpf的虚拟机仍然简单明了,它包含了一个累加器, 索引寄存器,一个暂存器,以及一个隐式程序计数器。这可以从以下的例子中看出来,在寄存器代码中呈现,如下是匹配所有的ip包,右边有每行代码的解释。 ldh [12] //将ethertype字段加载到寄存器当中。 jeq #ip包, L1,L2 //将寄存器ip与ethertype ip进行比较。 L1:ret #TRUE //如果进行比较为true,则返回true。 L2:ret #0 //如果比较是false,则返回0. 仅采用了4个虚拟机指令,BPF就实现了一个非常有用的ip包过滤,为了指明这种设计的效率是多么高,最初的bpf拒绝一个封包实现需要依靠184个 cpu指令,这是从bpf_tap调用开始到结束的指令指令计数的,这意味着它既测量了BPF实现又测量了BPF filter。bpf使处理器能够与新图形计算器 中的处理器相媲美,通过安全的内核虚拟机,能够处理理论上最大2.6Gbps的网络流量。可以通过与上下文切换的性能来进行对比,在 SPARCstation 2 上, McCanne and Jacobson进行了测量,the hardware used by McCanne and Jacobson to do these measurements, doing a context switch could take anywhere between 2,840 instructions and 16,000+ instructions (equivalent 71 microseconds and 400+ microseconds), 取决于资源对于竞争进程的数量需要交换的上下文。 最终,有两件事情在McCanne and Jacobson的parper上需要注意,在论文发表时,bpf已经诞生有两年了,这值得注意是因为这表明bpf发展是渐进的, 随着技术成功的发展。第二是,当中有提到,tcpdump是bpf程序中最为广泛采用的。这意味着,被广泛采用的tcpdump已经被使用了超过21年了, 我之所以提到这个是因为没有其他文章谈到bpf家族使用历史。当McCanne and Jacobson发表文章时,alpha阶段的实现bpf并不是一个好主意,它已经被广泛的测试 使用并且演进为了其他的工具。 McCanne and Jacobson的文章只有11页,感兴趣的话值得一读。 ebpf and linux: 现状 在2014年发表的linux内核版本3.18其中包含了第一个BPF的实现,简短说来,是一个内核虚机,就像bpf,但提供了关键的改进, ebpf比bpf更快了,得益于jit对于ebpf代码的编译。第二点是,被设计为了处理内核通用的信息,这允许了内核开发人员将ebpf整合到内存中以用于 他们认为合适的用途。第三,包括了高效的全局数据存储叫做ebpf map,这允许在事件之间被存储,从而可以聚合用于包括统计和上下文感知用途。 值得一提的是,自从ebpf被创建以来,这里与许多的数据存储类型,并不是所有的类型都是map,但本文也折中采用map来指代。 但随着这些能力,内核开发者迅速的被各种各样的方式使用到内核中,在一年半不到的时间内使用覆盖了网络监控,网络包封装,以及系统监控,因此ebpf程序 可以被用来切换网络流量,测量硬盘读取延迟,或者追踪cpu缓存丢失。 理解bpf亮点最快的方法以及目前的实现是浏览一遍使用bpf需要什么,bpf的整个流程可以分为一下三个不同的阶段。 1:创建bpf程序为字节码,目前创建bpf程序的标准方式是先编写c代码,然后在一个ELF的文件里面生成ebpf字节码,然而,ebpf虚拟机的设计 有非常完善的文档以及代码,许多工具都开源了,因此,对于起步来说,他完全可以手动编写一个ebpf程序的寄存器代码,或者是写一个自定义的 编译器。由于ebpf极其简单的设计,程序的所有函数,除了入口点函数,都必须内联以供llvm编译。本系列将在未发布的文章中更详细的介绍这点,与此同时, 如果想要寻求更多的有关信息,请查阅文末扩展阅读。 2:把程序加载进内核并创建必要的ebpf-map。这在linux系统中是通过bpf系统调用完成的,这个调用允许程序伴随bpf类型声明被加载。在写作这篇文章 时,ebpf程序有以下几种用法,作为socket filter,kprobe handler,traffic controll scheduler,traffic control action,tracepoint handler,traffic eXpress Data Path,performance monitor,cgroup restriction,以及light wight tunnel,调用同样被用于初始化bpf-map,第二篇文章将会详细介绍这些。 第三部分通过使用linux下工具来完成工作,主要是来自于iproute2的tc与ip, 3: 将被加载的程序连接到系统当中,不同的bpf程序可以被用到加载到不同的linux系统中,因此不同的系统也执行不同的加载过程。 当bpf程序被attach之后,它就会运行并开始filtering,analyzing,或者捕获信息,这取决以它创建的用途,从这里开始,用户空间的程序可以管理bpf程序, 包括从bpf-map中读取状态,以及如果是要操作ebpf来改变程序的状态,对于更多信息,请查阅文末扩展阅读。 上述3个步骤在概念上很简洁但实际使用中非常微妙。同时,当前的许多工具确实为使用ebpf提供了很多的帮助。即使可能使用起来感觉相反,就像linux中许多事情一样, 相比于在内核中使用ebpf程序明显要好于在内核外部不依靠任何工具。重要的是要记住,ebpf的通用性既是导致其惊人灵活性的原因,也带来了复杂性, 对于那些想要围绕bpf构建新功能的人来说明,目前linux内核提供了一个框架而不是工具箱之外。 做一个快速的概括,ebpf自从2014年进入到内核当中的用来做高效的事件处理并且已经有了广泛的使用,感谢ebpf-map,用ebpf编写的程序可以维护一个状态从而跨事件聚合信息,并且具有动态行为, 因为其闪电般的速度与性能,以及极简的实现,别越来越广泛的使用,现在我们来看下未来对于ebpf程序的期望吧。 Smart NICs与内核空间程序:未来 只花费了少数几年,ebpf就已经整合进了linux的内核中,这也让ebpf的内核变得更有趣且方向更不确定,但这里确实有两个方向,一个是对于bpf程序的使用, 二个是将ebpf作为工具。已经的将来ebpf的用途是确定的,即是将bpf程序用于安全,高效,以及内核的事件处理,因为ebpf是依靠运行它的虚机 去定义的,这也为ebpf使用在出内核的其他地方提供了潜力,在写作这篇文章时,最有趣的例子是智能网卡。 智能网卡是一种允许处理网络流量的网卡,在不同程度上,将负载卸载到网卡本身,这个想法大约在两千年左右形成,当一些nic支持卸载校验和与分段时, 但是直到最近才出现了部分或者全部的数据平面卸载,这些新的nic有许多的名字,包括智能服务适配器,但通用的特性是可编程功能,以及大量的内存用于存储状态, 这样的一个例子是Netronome 的 Agilio CX 系列网卡,具有10Gbps至40Gbps端口与ARM11处理器,2GB的DDR3内存,以及100多个专业处理核心。 因为他们具备大量的处理能力,最近智能网卡变成了ebpf的目标,通过采用ebpf,他们提供了各种各样的用途,包括缓解DDos攻击,提供动态网络路由,切换,负载均衡等, 例如,在2016年的netdev 1.2会议上,Netronome 的 Jakub Kicinski 和 Nic Viljoen发表了一份名为通过ebpf/xdp卸载到智能网卡,Nic Viljoen 在其中陈述了一些非常粗略的早期基准,即在 Netronome SmartNIC 上为每个 FPC 实现每秒 300 万个数据包, 正如 Viljoen 继续指出的那样,每个 SmartNIC 都有 72 到 120 个这样的 FPC,假设但可能不切实际,最大 eBPF 吞吐量为 4.3 Tbps 最终是bpf作为一种技术的未来,因为ebpf的限制与简单的实现,它提供了非常便携与高效的方式去处理事件,除此之外,bpf还强制改变了问题解决的思路, 它以除了目标以及有状态的代码,而是选择功能与高效的数据结构去存储状态,这种范式极大的缩减了程序设计的可能性,但这样做同样让他跟任何一种程序设计的 方法兼容,因此ebpf可以被用作同步,异步,并行,或者分布式的(取决于数据存储时协作的需求)。以及其他程序设计的方式。基于这一点事实, 我认为适合bpf的名字是功能虚拟机,简称FVM。 bpf带来的技术变革可能也是bpf最容易遗忘的东西,通过JIT编译将字节码编译为机器码在内核中执行,由于内核对用户态程序硬件隔离的巨大成本, 有时性能的损失达到了25%到33%,通过bpf被避免了,这意味着通过在内核空间虚拟机上运行用户程序可以将运行速度提高 50%!这个想法,事实上,并不是一个新的想法, 在2014年,Gary Bernhardt在pycon上有一个有趣而又引人入胜的谈话,标题为“javascript的诞生与死亡”,在当中,他引用了硬件隔离带来的统计性能损耗,并且解释到, 在假象的未来,所有软件中的大多数都运行在一个内核的javascrit虚拟机中,在这样的未来中,软件的可移植性远远不是什么问题,因为软件并不编译为特定的硬件架构,而是javascript, 他甚至继续谈到了文档模型的实现(DOM),数据存储在浏览器当中来保存状态用于渲染,现在运行在窗口系统也移动到内核中,这在概念上跟ebpf是非常接近的,理论上在将来可以被用到类似的事情, 虽然他在某些方面很轻松,不可否认的是Bernhardt的演讲是基于事实并且也是今后计算机程序通过内核空间虚拟机完成隔离而不是硬件隔离的方向。 我们只需要拭目以待,看看ebpf是否可以成为这一变革的拥护者! 结语:ebpf发展非常迅速,并且确实是给人一种magic的感觉,哈哈。