查看源代码 驱动程序

本节简要概述如何编写高效的驱动程序。

假设您对驱动程序有很好的理解。

驱动程序和并发

运行时系统在运行驱动程序中的任何代码之前始终会获取锁。

默认情况下,该锁是在驱动程序级别,也就是说,如果多个端口已打开到同一个驱动程序,则一次只能运行一个端口的代码。

可以将驱动程序配置为每个端口都有一个锁。

如果驱动程序以函数式方式使用(即,不保持状态,而只进行一些繁重的计算并返回结果),则可以预先打开多个带有注册名称的端口,并且可以根据调度器 ID 选择要使用的端口,如下所示:

-define(PORT_NAMES(),
	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).

client_port() ->
    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
	    ?PORT_NAMES()).

只要调度器不超过 16 个,驱动程序的端口锁就不会有任何锁争用。

调用驱动程序时避免复制二进制文件

基本上有两种方法可以避免复制发送到驱动程序的二进制文件

  • 如果 port_control/3Data 参数是一个二进制文件,则驱动程序将传递一个指向二进制文件内容的指针,并且该二进制文件不会被复制。如果 Data 参数是一个 iolist(二进制文件和列表的列表),则 iolist 中的所有二进制文件都将被复制。

    因此,如果您想在不复制二进制文件的情况下将预先存在的二进制文件和一些额外数据发送到驱动程序,则必须调用 port_control/3 两次;一次使用二进制文件,一次使用额外的数据。但是,这只有在只有一个进程与端口通信时才有效(因为否则另一个进程可能会在调用之间调用驱动程序)。

  • 在驱动程序中实现一个 outputv 回调(而不是 output 回调)。如果驱动程序具有 outputv 回调,则在 port_command/2Data 参数中以 iolist 传递的 refc 二进制文件将作为引用传递给驱动程序。

从驱动程序返回小型二进制文件

运行时系统可以将高达 64 字节的二进制文件表示为堆二进制文件。当在消息中发送它们时,它们总是被复制,但是如果它们不发送到另一个进程并且垃圾收集更便宜,则它们需要更少的内存。

如果您知道您返回的二进制文件总是很小,建议您使用不需要预先分配二进制文件的驱动程序 API 调用,例如,driver_output()erl_drv_output_term(),使用 ERL_DRV_BUF2BINARY 格式,以允许运行时构造堆二进制文件。

从驱动程序返回大型二进制文件而不复制

为了避免在大型二进制文件从驱动程序发送或返回到 Erlang 进程时复制数据,驱动程序必须首先分配二进制文件,然后以某种方式将其发送到 Erlang 进程。

使用 driver_alloc_binary() 来分配二进制文件。

有几种方法可以发送使用 driver_alloc_binary() 创建的二进制文件