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