eBPF的发展演进从石器时代到成为神(一)

2023年 11月 25日 78.9k 0

1. 前言

技术的发展往往是积跬步而至千里的。Linux从92年诞生,发展至今已经覆盖大小各类的信息基础设施。是什么样的力量,让Linux能够始终保持发展活力,又如何看待Linux之上出现的新的技术趋势?

 

本文试图通过梳理eBPF的演进过程,探索Linux内核的发展动力来源与发展轨迹,与大家一同畅想eBPF给内核技术、Linux生态带来的全新变局。

 

2. eBPF概览

2.1.
实现原理

 

大家可能都知道图灵机,这是一个可计算理论模型,可以用来判断计算机的计算能力。图灵机是目前有可能实现的计算能力最强的理论模型,目前我们常用的计算机,理论上都是等价于图灵机的。

 

BPF的出现,是对计算能力的渴求,其原理就是通过IR模拟一台RISC指令集的计算机嵌入到内核中,将内核内部的静态编译逻辑转变为更加灵活的动态编译逻辑,使内核获得近似于图灵机的动态逻辑定制能力。而从classic BPF到extended BPF的发展,是将这一计算方式进一步夯实和通用化。

 

BPF的出现乃至到eBPF的进一步发展,为内核带来了巨大的改变,使内核具备了更加强大、可编程的动态变化的能力。这种能力在各种需要定制化的应用场景中,将发挥巨大的价值,既可以用于扩展功能,也可以用于优化性能。

 

在实现上,为适应不同业务场景的需求,使eBPF具备等价于一台RISC指令集计算机的计算能力,通过输入参数、Map数据存储、Helper帮助函数,构成了eBPF程序与内核交互的运行环境。eBPF指令集的计算和控制能力、运行环境与内核的交互能力,两者叠加构成了eBPF程序强大的处理能力。

 

在安全方面,通过Verifier严格检查eBPF程序的可完成性、数据访问的合法性等,保证了eBPF程序与内核交互过程中内核不被挂起、核心数据不会被破坏。

BPF发展过程中,由cBPF发展成为eBPF是一次大的技术升级。eBPF在cBPF的基础上重新设计了指令集、引入了JIT、增加了辅助函数,大大扩展了复杂逻辑的设计能力。虽然eBPF有巨大的进步,但是基本的底层设计还是一致的,因此两者统称为BPF。

 

由于eBPF兼容cBPF,在未指定时,BPF更多指eBPF所定义的内涵。后文用BPF泛指整个BPF相关的基础机制,eBPF特指最新的BPF标准。

 

2.2.
技术特点

BPF还在快速发展,它的计算能力和完备性也在迅速提高,前景无限。但就具体的版本而言,却又呈现具体技术特点,主要是其支持的能力和受到的约束两个方面。以最新的BPF的技术标准(v6.1)为蓝本,介绍BPF的主要技术特点。

 

  • RISC指令集

BPF的核心是一个虚拟计算机,它采用类RISC指令集,支持跳转、算数运算、尾调用等基本操作。在运行BPF程序的计算机上,BPF指令会被内核的JIT编译器动态编译为物理机原生指令,实现运行效率的“零”损耗。在支持BPF卸载的设备上,BPF程序也可以卸载到设备上执行。在BPF的指令集中还支持伪调用指令,可以调用到内核帮助函数。

同时,BPF的指令的编码空间中还有大量的储备,未来根据需要一定还会继续增加指令,提升BPF实现复杂逻辑的能力。

  • Map

基于键值对的数据存储机制,可用于实现内核、用户态的数据存储和交换。

  • Helper函数

专用于BPF程序调用的函数接口,用于封装内核中的功能,使BPF程序可以和内核互操作,同时保持BPF程序和内核的安全隔离。

  • BPF子程序

实现了BPF程序之间的调用。

  • 上下文

BPF程序的语境和运行上下文,是一种内部透明的数据结构。只有在明确BPF程序的类型时,上下文的定义和内部数据结构才是确定的。不同的BPF程序类型,上下文也各不相同。

  • CO-RE

通过运行时类型支持,实现一次编译、随处运行。

  • 支持特权和非特权级两类运行模式

分为特权级(百万ins)和非特权级(4096ins)两类运行方式。

特权级模式下BPF程序可以获得更宽的权限,实现更复杂的逻辑功能。

  • 保证向后兼容

这一原则对于BPF的推广应用非常重要,可以保证旧标准的BPF程序在新标准下也可以正确执行。但同时,也对未来BPF发展带来了约束,只有把握好BPF的发展方向,做好底层设计,才能两者得到兼顾。

比如,从老版本遗留下来的cBPF程序在eBPF中都会被JIT正确翻译和执行。

  • 稳定的ABI

BPF稳定的ABI包括,BPF程序类型对应的输入参数定义,可调用的内核帮助函数定义,返回值定义等。使用稳定的ABI的BPF程序,可保证与不同版本的内核都是兼容的。

另外,BPF还在快速发展中,它的功能特性需要逐步释放,因此目前还有诸多限制,其中有些是基于安全、可靠性考虑,有些是没有超出范围的应用需求的保守设计等等。随着安全机制的完善、应用程序的扩展、生态体系的成熟,相应的限制也会逐步的改变。

目前的实现中,有如下限制:

  • 总运行时间有界

有界性这是基本原则,应该在比较长的时间内都不会改变。但是,在不改变有界性的前提下,根据具体需要适当调整更合理的上限,这是存在极大可能的。

  • 指令总数限制

非特权用户最大指令数4096,特权用户最大指令数1百万。

  • 分支数限制

  • BPF调用嵌套层次限制

  • Map实例数限制

  • 验证状态数限制

  • 最大分支数限制

  • 堆栈长度限制

目前支持的堆栈最大长度为512字节。

  • 上下文限制

每一种类型的BPF程序,都有其对应输入参数定义,彼此不同。也就是说,BPF程序只能接受特定的输入并进行处理,不能访问内核的全部状态空间。

  • 辅助函数限制

每一个BPF程序类,都有其对应的辅助函数集合。这些辅助函数,由内核各子系统提供,是BPF程序类上下文的一部分。它们帮助BPF程序与内核各子系统交互,同时又保护内核不会被破坏。

上面赘述了很多特性,大家可能会有很多疑问,比如:

为什么采用精简指令集呢?因为这是目前最主流的指令集类型,相对于复杂指令集,精简指令集更有利于实现更高密度、更高吞吐量、更高主频的处理器。因此x86之后出现的新型指令集系统,绝大多数都是精简指令集,包括现在的开源指令集RISC-V。

为什么不采用原生的指令集呢?

为什么5个参数寄存器呢?

本篇暂不深入讨论,后续主题涉及到的时候再详细讲解。

 

2.3.
应用价值

BPF的应用价值与其动态和可定制特性强相关。

内核研发中一直坚守的原则是:“机制与策略分离”,即:内核负责提供机制,将策略开放给上层。在机制与策略之间需要一层界面来进行交互。

系统调用是最初方案。它是单向发起的,缺少事件模型。

虚拟文件系统,提供了双向的交互方式,但难以灵活定制复杂的逻辑。

由于软件功能越来越复杂,无法用简单规则来表达,软件的基础功能设施与业务逻辑,需要进行解偶。而业务逻辑部分,需要根据业务定制,因此很适合用BPF实现。比如:

  • 过滤器

  • 权限检查

  • 模糊测试

等类型的功能,比较适合用BPF实现。另外,视具体问题,也可以应用于:

  • 调度算法

  • 用户态交互(替代系统调用,实现更加可变的服务逻辑)

  • 加载器、模拟器、兼容层

  • 轻量化内核

  • 多态内核

  • 启动方式

每一种业务类型都有其独具特征的逻辑模型,通过更形式化地定义这些业务模型,可以更好地理解它们和BPF的结合性,找到更好的实现方案,充分发挥BPF带来的强大能力。后续篇章,我们会对典型的应用模型进行更深入的讨论,以及BPF在这些应用场景中,应该在哪些特性方面进行加强或改进。

相关文章

塑造我成为 CTO 之路的“秘诀”
“人工智能教母”的公司估值达 10 亿美金
教授吐槽:985 高校成高级蓝翔!研究生基本废了,只为房子、票子……
Windows 蓝屏中断提醒开发者:Rust 比 C/C++ 更好
Claude 3.5 Sonnet 在伽利略幻觉指数中名列前茅
上海新增 11 款已完成登记生成式 AI 服务

发布评论