9.驱动 | 9. Drivers
9名司机
本节简要介绍如何编写高效的驱动程序。
假设你对司机有很好的理解。
9.1驱动和并发
运行时系统在运行驱动程序中的任何代码之前,总是先获取一个锁。
默认情况下,该锁位于驱动程序级别,也就是说,如果多个端口已被打开给同一驱动程序,则只能同时运行一个端口的代码。
可以将驱动程序配置为为每个端口设置一个锁。
如果以函数方式使用驱动程序%28,即不保存状态,但只进行一些繁重的计算并返回结果%29,则可以预先打开多个注册名称的端口,并可以根据调度程序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个调度程序,驱动程序的端口锁就不会有任何锁争用。
9.2在调用驱动程序时避免复制二进制文件
基本上有两种方法可以避免复制发送给驱动程序的二进制文件:
- 如果
Data
主张port_control/3
是二进制文件,驱动程序将被传递指向二进制文件内容的指针,并且二进制文件不会被复制。如果Data
参数是一个iolist%28列表的二进制文件和列表%29,所有二进制文件都将被复制。 因此,如果您想在不复制二进制文件的情况下同时向驱动程序发送预先存在的二进制文件和一些额外的数据,则必须调用port_control/3
两次;一次使用二进制文件,一次使用额外的数据。但是,只有当只有一个进程与端口%28通信时,这才能工作,因为否则另一个进程可以在调用%29之间调用驱动程序。
- 实现
outputv
回调%28而不是output
在驱动程序中回拨%29。如果司机有outputv
回调时,REFC二进制文件在Data
主张port_command/2
将作为对驱动程序的引用传递。
9.3从驱动程序返回小二进制文件
运行时系统可以将最多64个字节的二进制文件表示为堆二进制文件。它们总是在消息发送时被复制,但是如果它们没有被发送到其他进程,并且垃圾收集更便宜,则需要更少的内存。
如果您知道您返回的二进制文件总是很小,则建议您使用不需要预先分配的二进制文件的驱动程序api调用,例如,driver_output()
或erl_drv_output_term()
,使用ERL_DRV_BUF2BINARY
格式,以允许运行时构造堆二进制文件。
9.4返回大型二进制文件而不从驱动程序复制
为了避免在将大型二进制文件从驱动程序发送或返回到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项中。