驱动程序堆栈性能

本文档旨在简要介绍在 Fuchsia 中编写新驱动程序或与现有驱动程序互动时,在性能方面应遵循的良好实践和应避免的错误实践。

本文档涵盖以下主题:

API 定义

在为新的驱动程序堆栈编写 API 时,应考虑一些关键的性能分析。驱动程序堆栈 API 属于应用或驱动程序类别。

应用 API 用于让常规非驱动程序组件访问硬件资源,而驱动程序 API 用于让驱动程序彼此通信。例如,fuchsia.hardware.network 是用于与网络驱动程序交互的应用 API,而 fuchsia.hardware.network.driver 定义了网络设备的较低级别驱动程序 API,该 API 使用驱动程序传输来通过并置实现更高效的驱动程序间调用。

设备驱动程序 API 通常是 fuchsia.hardware.* FIDL API。

避免同步操作

属于快速路径的工作单元应避免期望同步完成,尤其是在操作需要跨越进程边界时(但不限于此)。对于 FIDL API,不必要的同步性可能源于这样一种设计:在完成上一个工作单元之前,无法启动新的工作单元,这意味着调用方必须空闲等待,直到可以安全地请求下一个工作单元。

从性能角度来看,在设置、拆解和配置等慢速路径操作中,同步操作是可以接受的。

鼓励批处理

如果 API 定义明确定义了一批工作,而不是始终传输单个单元,则建议 API 用户通过其应用或驱动程序实现批处理。

批处理的一个重要特点是,对于一定量的工作,它可减少 API 的调用次数。对于跨进程边界的 API,这会直接转化为减少的系统调用和调度开销,从而有助于提升性能。

此外,如果 API 定义本身以批处理方式提供工作单元,设备驱动程序可以通过硬件加速更轻松地将一批工作项合并为一个单元。许多设备驱动程序都使用 DMA,例如,通过 DMA 将工作加入到它们所驱动的特定硬件的队列中。如果一次收到一批工作项,则可以减少与硬件的交易次数。如果没有明确确定的批处理边界,设备驱动程序就不得不比必要时更频繁地与硬件交互,或者采用启发式方法(例如轮询间隔)来减轻硬件通信负担。

避免数据复制

在网络、音频或视频等高带宽或低延迟应用中,避免数据复制尤为重要。大型载荷应尽可能在 VMO 中跨越 API 边界。一种常见策略是在设置时协商有限数量的 VMO,并在操作期间交换对这些 VMO 中区域的引用。

阐明了流控制

如果 API 批处理正常运行,并且不会像上文建议的那样设置严格的同步操作预期,那么控制 API 服务器和客户端之间的信息流对于正确性和性能都非常重要。

在正常运行中,API 的流控制定义必须允许所有部分能够执行工作,而不会因等待其对等方而受阻,方法是保持有关系统能够执行的总工作量的一些不变量(由定义固定或在设置时预先协商)。

例如,fuchsia.hardware.network 通过在设置期间定义有限数量的“工作单元”(即网络数据包)来强制执行流控制。在任何时间点,双方都可以仅使用本地维护的状态来了解仍有多少数据包可以跨 API 边界推送。

用于订购的账号

在某些应用中,所有工作单元必须按设定的顺序执行,才能确保正确性。不过,在其他应用中,某些排序限制可能会放宽或完全解除。

如果工作项流不必按确切顺序执行,API 可以反映这一点,以便驱动程序和应用轻松识别可以并行化的工作。

例如,网络数据包通常需要保持顺序,以免破坏应用协议,但这种情况通常仅适用于每个应用流实例(称为“流”)。网络适配器中常用的策略是定义一组数据包队列,并将每个应用流确定性地分配给其中一个队列。然后,网络堆栈可以安全地并行操作队列,而无需查看数据包的内容。观察这些常见的硬件设施,并在 API 中尽可能充分地利用它们,可以带来显著的性能提升。

实现

本部分列出了在实现提供或使用设备驱动程序 API 的代码时需要注意的性能模式和反模式。实现可以是驱动程序本身,也可以是与设备驱动程序提供的服务互动的应用。

在编写快速路径上的代码时,应始终遵守以下规则。请注意,对于设备驱动程序实现,快速路径的一部分通常位于中断线程中。

  • 避免分配内存或创建 Zircon 对象。快速路径上使用的资源应始终预先分配。使用 FIDL 绑定(尤其是 hlcpp)或 fit::function 等可能会导致隐式分配的库时,请务必格外谨慎。
  • 注意系统调用。系统调用操作成本高昂,因此在对性能敏感的操作中,必须非常谨慎,避免随意调用内核。完全消除系统调用可能无法实现,但减少每个工作单元的调用次数(例如通过批处理)可以大大降低系统负载。
  • 快速返回共享资源。如果 API 定义了有限的共享资源池(例如共享内存区域),则应尽可能快地重用这些资源或将其返回到“可用”池,最好以批处理方式进行。
  • 尽可能以只读模式而非读写模式映射 VMO。以只读方式映射 VMO 可减少需要为 DMA 执行的缓存操作次数。例如,当使用 DMA 写入可能具有脏缓存行的共享 VMO 时,必须在 DMA 之前和之后刷新并使缓存失效。另一方面,对于只读映射,缓存行始终是干净的,这意味着它们只需要在 DMA 完成后刷新并失效。