eBPF 的发展演进 --- 从石器时代到成为神(四)
5.eBPF 的意义
BPF 最初来源于解决网络报文过滤的问题,实现灵活的过滤规则。网络报文的过滤规则,最初只需要正则语言就能表达,但后来就不够了。而 BPF 提供了更强大的表达能力,BPF 具有近似图灵完备性,必将成为问题分解、解决复杂问题的神级工具。
5.1.图灵完备性
讨论 BPF 的计算能力,涉及到图灵完备。BPF 目前的基本设计中,有限性是基本设计原则,这是保证内核不被扩展逻辑挂死的基本要求。而有限性,是 BPF 和图灵机的根本差异,因此它不是图灵完备的。这个结论固然没错,但如果讨论仅止于此的话,那么这一论断过于粗糙,换个有趣一点的说法,这样的讨论不是图灵完备的,因此还需要具体分析。
完备性,不是评价工具优劣的完全准则。一般认为,C 语言是图灵完备的。但是 C 语言的所有数据类型都是有界的,其实是弱于图灵机的。但不妨碍人们认为 C 是图灵完备的,因为它的能力边界距离实际应用的需求很远,我们感受不到。虽然 C 语言图灵不完备,但是不妨碍它的发展潜力,在它的成长过程中,也在不断的改版、丰富。是因为它的完备性不足吗?显然不是。一种工具,在工程实践中,完备性是次要的,因为他被选择,就说明它是够用的。其他方面才是当下更应该关注的问题。
图灵机是一种无限的自动机,人们穷尽办法也只能逼近,即使全世界所有计算机加在一起的总和,也弱于图灵机。所以图灵完备现实中根本不存在,讨论逼近图灵机的能力可能更现实。在实际的语境中,人们实际上把无限接近图灵机的逼近能力,等同于图灵完备性。一个很好的例子就是 C 语言,它显然不是图灵完备的,但人们一般认为它是图灵完备。从这点说,BPF 语言同样是图灵完备的。
排除语言的问题,那么 BPF 是图灵完备的吗?仍然不是,BPF 的图灵不完备,并不主要来源于 BPF 语言本身,而是来源于运行环境。从这点说,BPF 语言是图灵完备的,BPF 虚拟机不是。从这点也可以说,只要有需要,通过改造运行环境,BPF 可以无限逼近图灵机的计算能力。
因此,从图灵完备这一点,我们既不能过度的否定 BPF,认为它的能力有限。但同时,也不能认为它的能力可以无限扩张,因为需要满足特定的条件。总之,BPF 还在快速发展过程中,一切可能性皆在其中,任何定论皆言之过早。
从另一个角度,就 BPF 目前的应用领域来说,输入是有限的、状态空间是有限的,因此在有限的输入下,图灵完备并不是必须。这是从现实的需求来说,BPF 足以完成指定语境下的任何计算。
但显然 BPF 的计算能力还有很大的提升空间。
语言方面,BPF 的指令集的提出,在计算能力上,它就是超配的。现在的问题是,如何安全地释放他的能力。运行时系统和工具链的设计,是目前的焦点问题。已经呈现出思想分歧,基于运行时环境的思路和直接开放的思路同时存在。未来这两个思路应该都会有一定程度的发展,形成面向不同领域的高低搭配的解决方案。
因此,我认为运行时的改进可能更加迫切。这需要我们及早确定问题边界,提供面向问题的运行环境,才能更有效的提出平衡安全和可计算性的问题的方案,即:运行环境+必要的计算能力,构成完备的面向问题域的解决方案。定义一个安全的虚拟机,保证操作不逃逸,一个安全的运行时库,导出或者链接内核对象(Helper),在这个集合上,定义安全的操作,这样语言本身就可以不再受具体逻辑和访问对象的限制,做到语言本身的图灵完备。
5.2.编程模型的发展
在 BPF 之前,Linux 开发的编程模型,可以分为内核编程和用户态编程两种。分别使用不同的编程接口和编程规范,是两者最大的区别。
BPF 出现之后,出现了新的编程模型,既不能称之为内核编程,也不能称之为用户态编程。
这是一种全新的编程模型。它运行于内核态,但是不使用任何传统的内核接口(5.13 可以调用经过筛选和处理的内核函数。至今,它仍然受限于特定函数和指定的上下文,还不是一种通用的机制。且这种机制进一步通用化之前,它的安全性仍然值得先进一步的讨论),不通过符号与内核进行链接。它使用应用编程逻辑和范式,但是不使用应用编程传统的接口,而是使用 BPF 提供的帮助函数。它所能访问的数据对象还在不断发展过程中,远未定型。
因此,笔者称这种编程模型为:临界编程。也许它未来会有更好的名字,但这个名字一方面,表明它的跨界特性,一方面表面它日新月异的发展。也表明对它未来的期待。
5.3.用户态比重的加大
由于虚拟化和软件工程的原因,网络报文处理和文件系统,呈现出往用户态迁移的趋势。
BPF 和用户态化的共通点和差异点在于,都将更多的内核扩展性放在了用户态,但 BPF 的逻辑仍然从属于内核。
他们都和传统内核通过一层良好定义的接口进行了隔离。用户态驱动和文件系统,使内核的功能更容易扩展。而 BPF 则是对内核本身的扩展。两者存在根本差异,因此也存在相互结合的可能,从而形成更加强大的软件架构。
而这种架构会用于什么地方呢?
我们已经做了初步尝试,FUSE 和 BPF 进行结合。可以实现用户态文件系统和内核更加高效的交互(这一话题,我们在后续的篇章中再详细讨论)。
推而广之,内核的网络、安全、文件系统、驱动,都可以放在用户态来实现,通过 BPF 来优化交互。
5.4.微内核
BPF 的运行基础是运行时环境,随着 BPF 应用的增加,一定会促使内核子系统的更进一步的抽象和解偶,这在逻辑上为微内核化准备了条件。
BPF 真正避免了纯粹用户态编程的性能问题,为应用开发人员开发特色功能提供了一种临界编程工具。这或许是微内核的另一种实现路径。
5.5.观测代码与业务代码合一
BPF 出现的时候,最初是观测工具,但后来它也能用于实现更复杂的功能,影响网络子系统的报文转发逻辑。
BPF 计算能力的强大、性能的优势,使它不仅能用于观测还可以做更多复杂的事情。
通过高度抽象化的设计,我们可以设计出复杂、通用的业务系统,但是我们设计不出“最佳”的业务系统。最佳的业务系统一定是在真实的应用场景中,通过不断的观测、分析、优化,才能达成的。
将一个复杂系统优化到“最佳”同样是一个复杂问题,多目标的一致性、动态系统的不稳定性、巨大的状态空间等等,都可能导致这个问题没有最终答案,只有采用动态反馈机制。因此,将观测代码和优化代码(业务代码的策略优化部分)合一,是使这一优化模式能够更加准确、高效、稳定的必然选择。
5.6.编译器和内核合一
从本质上讲,计算问题、语言问题其实是一个问题。最初我们解决计算问题,是在纸带上打孔,后来有了编译器。解决计算问题的效率大大提升,但是解决计算问题的能力其实没有变化。
后来有了操作系统,软件的分层模型逐渐成型,开发应用程序的效率大大提升,但其实通过编程解决计算问题的能力并没有提升,反而是在下降。因为软件的每一个分层,在带来工程化效率的同时,也导致了能力的损耗。API 的设计是一个大命题,但是没有完美的 API 设计。
开发效率的提升,带来了应用的高度发展,现在计算能力的问题出现了。回归本原,将编译器和内核合一,构建更加强大的计算能力,是未来发展的基础。
评论