驱动程序调度程序和线程

调度程序可让驱动程序在驱动程序主机中提供的线程上调度异步工作。这些共享线程由驱动程序运行时管理,有助于在驱动程序主机的驱动程序中实现更高的整体性能和线程安全性。强烈建议驱动程序生成自己的线程。

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() 函数)的一部分提供给驱动程序。这会成为该驱动程序的默认调度程序。为了检索此调度程序,驱动程序可以在驱动程序钩子执行期间调用 fdf::Dispatcher::GetCurrent() 函数,例如 Start()PrepareStop()Stop()

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

正在关闭调度程序

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

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

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

创建更多调度程序

驱动程序只能在调度程序回调期间创建额外的调度程序,即当调度程序调用驱动程序时(有关发生这种情况的示例,请参阅调度程序操作)。与默认调度程序不同,这些附加调度程序拥有并管理这些附加调度程序的生命周期。在 DFv2 中,驱动程序主机会在调用驱动程序的 Stop() 钩子之前自动关闭驱动程序的其他调度程序。