查看源代码 驱动程序
本节简要概述如何编写高效的驱动程序。
假设您对驱动程序有很好的理解。
驱动程序和并发
运行时系统在运行驱动程序中的任何代码之前始终会获取锁。
默认情况下,该锁是在驱动程序级别,也就是说,如果多个端口已打开到同一个驱动程序,则一次只能运行一个端口的代码。
可以将驱动程序配置为每个端口都有一个锁。
如果驱动程序以函数式方式使用(即,不保持状态,而只进行一些繁重的计算并返回结果),则可以预先打开多个带有注册名称的端口,并且可以根据调度器 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/3
的Data
参数是一个二进制文件,则驱动程序将传递一个指向二进制文件内容的指针,并且该二进制文件不会被复制。如果Data
参数是一个 iolist(二进制文件和列表的列表),则 iolist 中的所有二进制文件都将被复制。因此,如果您想在不复制二进制文件的情况下将预先存在的二进制文件和一些额外数据发送到驱动程序,则必须调用
port_control/3
两次;一次使用二进制文件,一次使用额外的数据。但是,这只有在只有一个进程与端口通信时才有效(因为否则另一个进程可能会在调用之间调用驱动程序)。在驱动程序中实现一个
outputv
回调(而不是output
回调)。如果驱动程序具有outputv
回调,则在port_command/2
的Data
参数中以 iolist 传递的 refc 二进制文件将作为引用传递给驱动程序。
从驱动程序返回小型二进制文件
运行时系统可以将高达 64 字节的二进制文件表示为堆二进制文件。当在消息中发送它们时,它们总是被复制,但是如果它们不发送到另一个进程并且垃圾收集更便宜,则它们需要更少的内存。
如果您知道您返回的二进制文件总是很小,建议您使用不需要预先分配二进制文件的驱动程序 API 调用,例如,driver_output()
或 erl_drv_output_term()
,使用 ERL_DRV_BUF2BINARY
格式,以允许运行时构造堆二进制文件。
从驱动程序返回大型二进制文件而不复制
为了避免在大型二进制文件从驱动程序发送或返回到 Erlang 进程时复制数据,驱动程序必须首先分配二进制文件,然后以某种方式将其发送到 Erlang 进程。
使用 driver_alloc_binary()
来分配二进制文件。
有几种方法可以发送使用 driver_alloc_binary()
创建的二进制文件
- 从
control
回调中,如果set_port_control_flags()
已被调用并带有标志值PORT_CONTROL_FLAG_BINARY
,则可以返回一个二进制文件。 - 可以使用
driver_output_binary()
发送单个二进制文件。 - 使用
erl_drv_output_term()
或erl_drv_send_term()
,可以将二进制文件包含在 Erlang 术语中。