驱动程序调度程序和线程

调度程序使驱动程序能够在驱动程序主机中可用的线程上调度异步工作。这些由驱动程序运行时管理的共享线程可提高驱动程序主机中驱动程序的整体性能和线程安全性。强烈建议驱动程序不要生成自己的线程。

Fuchsia 的驱动程序框架提供 fdf::Dispatcher,建议将此调度程序与 Fuchsia 驱动程序搭配使用。此驱动程序调度程序支持以下功能:

调度程序操作

分配给驱动程序的调度程序会协调并执行驱动程序的异步操作。驱动程序宿主(因此,在同一进程中)中的所有调度程序都由驱动程序运行时管理的一组共享线程提供支持。调度程序只能在驱动程序运行时环境(例如驱动程序主机或驱动程序测试框架)中运行。

调度程序主要处理两项任务:调度异步工作以在驱动程序主机中的线程上运行(响应由驱动程序或驱动程序主机创建的回调),以及代表驱动程序在这些线程上实际运行工作(或“调用驱动程序”)。

在以下常见情况下,调度程序可能会调用驱动程序:

  • 运行驱动程序钩子(请参阅默认调度程序)。
  • 运行之前注册的异步操作的回调。
    • 已发布的任务已准备就绪。
    • 已发出等待信号。
    • 收到 FIDL 请求或响应。
  • 调度程序正在关闭(请参阅关闭调度程序)。

线程模型

驱动程序主机中的调度程序可以使用不同的选项来配置其自身的线程模型,以实现以下目的:

已同步和未同步

使用 FDF_DISPATCHER_OPTION_SYNCHRONIZED 选项创建的同步调度程序绝不会并行调用驱动程序。这可以看作是一个单线程调度程序,但需要注意的是,它不保证调度程序每次都从同一线程调用驱动程序(请参阅线程本地存储空间)。

使用 FDF_DISPATCHER_OPTION_UNSYNCHRONIZED 选项创建的非同步调度程序可能会并行调用驱动程序。不过,使用此选项时,无法保证顺序;驱动程序负责所有状态同步。因此,如果可能,最好使用同步调度程序来避免此类复杂性。

线程模型

图 1. 由同步和非同步调度程序调度的回调的时间轴。

如图 1 所示,同步调度程序不显示并行回调,而非同步调度程序显示多个并行回调。

同步操作

一般来说,不建议驱动程序进行同步调用,因为这可能会阻止其他任务运行。不过,如有必要,驱动程序可以使用 FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS 选项创建调度程序,该选项仅支持同步调度程序

此选项会在创建调度程序时在线程池中生成一个额外的线程。这有助于驱动程序运行时减少驱动程序引入的同步调用阻塞同一驱动程序主机中其他驱动程序的可能性。

阻塞任务

图 2. 通过同步调度程序(使用和不使用 FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS 选项)调度的回调的时间轴。

在图 2 中,左侧的方框(任务 A1 和任务 A2)表示非阻塞任务,而右侧的方框(任务 B1 和任务 B2)表示阻塞任务。线程 1 和 2 由驱动程序主机中的调度程序共享,用于运行阻塞任务和非阻塞任务。任务 B1 正在运行同步调用,从而阻止其他任务在线程 1 上进行调度。不过,驱动程序运行时可以在使用线程 1 的同时,自由地在线程 2 上调度异步任务。

线程本地存储

调度程序可以从驱动程序运行时管理的任何共享线程中调用驱动程序。不过,驱动程序运行时不保证每次调用都使用同一线程。因此,驱动程序不应使用任何线程本地存储(例如,在 C++ 中使用 thread_local 关键字),因为此类存储会将变量的生命周期与特定线程相关联。

重入保证

调度程序绝不会向驱动程序发出重入调用 - 如果驱动程序之前在同一调用堆栈中从调度程序收到过调用,则该调用会被视为重入调用。如果某个调用是可重入的,调度程序会将其安排在调度程序循环的未来迭代中进行。

调度程序的生命周期

驱动程序宿主管理驱动程序默认调度程序的生命周期。驱动程序宿主可保证在驱动程序停止之前,不会销毁此调度程序。

默认调度程序

在 DFv2 中,调度程序作为驱动程序启动钩子(即驱动程序代码中的 Start() 函数)的一部分提供给驱动程序。这会成为驱动程序的默认调度程序。为了检索此调度程序,驱动程序可以在驱动程序钩子(例如 Start()PrepareStop()Stop())期间调用 fdf::Dispatcher::GetCurrent() 函数。

在 DFv1 中,驱动程序宿主在绑定驱动程序时会为该驱动程序创建一个新的调度程序。为了检索此调度程序,驱动程序可以在驱动程序钩子设备钩子(例如 Bind()Unbind()Release())期间调用 fdf::Dispatcher::GetCurrent() 函数。

关闭调度程序

在 DFv2 中,驱动程序宿主会在调用驱动程序的 Stop() 钩子之前自动关闭驱动程序的所有调度程序。如果驱动程序希望在关机之前收到通知,可以实现 PrepareStop() 钩子。

在 DFv1 中,驱动程序的默认调度程序会在调用驱动程序主设备中的 Unbind() 钩子(这也会导致所有子设备解除绑定)之后但在调用 Release() 钩子之前自动关闭;驱动程序的主设备是驱动程序的 Bind() 钩子添加的设备。驱动程序必须处理其创建的任何额外调度程序的关闭。

当调度程序关闭时,它会调度所有状态为 ZX_ERR_CANCELED 的待处理回调,并调用提供给 fdf::Dispatcher::Create() 的关闭处理程序。

创建其他调度器

驱动程序只能在调度程序的回调期间(即调度程序调用驱动程序时)创建额外的调度程序(有关何时发生这种情况的示例,请参阅调度程序操作)。 与默认调度程序不同,驱动程序拥有并管理这些额外调度程序的生命周期。

在 DFv2 中,驱动程序宿主会在驱动程序关闭期间自动关闭驱动程序的其他调度程序。对于 C++ 驱动程序,此操作发生在 DriverBase::PrepareStop() 之后,但在 DriverBase::Stop() 之前。对于 Rust 驱动程序,此操作发生在 Driver::stop() 之后。