driver_entry
driver_entry
C库
driver_entry
库摘要
Erlang驱动程序使用的驱动程序入口结构。
描述
警告
使用此功能时要格外小心。
驱动程序回调是作为VM本地代码的直接扩展来执行的。执行不是在安全的环境中进行的。虚拟机无法
提供与执行Erlang代码时所提供的服务相同的服务,例如先占调度或内存保护。如果驱动程序回调函数运行不正常,则整个虚拟机将出现故障。
- 驱动程序回调--崩溃将使整个VM崩溃。
- 错误实现的驱动程序回调可能导致VM内部状态不一致,这可能导致VM崩溃,或者在调用驱动程序回调之后的任意一点上VM的各种错误行为。
lengthy work
在返回之前进行驱动程序回调会降低虚拟机的响应速度,并可能导致各种各样的奇怪行为。这种奇怪的行为包括但不限于极端的内存使用,以及调度程序之间的负载平衡不佳。Erlang / OTP版本之间因长时间工作而出现的奇怪行为也会有所不同。
从ERTS 5.9(Erlang / OTP R15B)开始,驱动程序接口已被更改为回调输出,控制和调用的较大类型。 请参阅erl_driver中的驱动程序版本管理。
注
旧驱动程序(使用早于5.9的ERTS版本中的erl_driver.h进行编译)必须更新,并且必须使用扩展接口(使用版本管理)。
该driver_entry
结构是所有Erlang驱动程序定义的C结构。它包含Erlang驱动程序的入口点,在Erlang代码访问驱动程序时由Erlang模拟器调用。
erl_driver驱动程序API函数需要一个端口句柄来标识驱动程序实例(以及模拟器中的端口)。 这只传递给启动函数,但不传递给其他函数。 start函数返回传递给其他函数的驱动程序定义的句柄。 通常的做法是让启动函数分配一些应用程序定义的结构并将端口句柄存入其中,以便稍后使用驱动程序API函数来使用它。
驱动程序回调函数由Erlang模拟器同步调用。如果它们在完成之前花费的时间太长,则会在模拟器中导致超时.。必要时使用队列或异步调用,因为模拟器必须响应。
驱动程序结构包含驱动程序名称和大约15个函数指针,仿真程序在不同的时间调用这些指针。
驱动程序唯一导出的函数是driver_init。 该函数返回指向驱动程序中其他函数的driver_entry结构。 driver_init函数使用宏DRIVER_INIT(drivername)声明。 (这是因为不同的操作系统有不同的名称。)
在用C++编写驱动程序时,驱动程序条目为"C"
联系。这样做的一种方法是在驱动程序输入之前放置以下一行:
extern "C" DRIVER_INIT(drivername
当驱动程序将driver_entry传递给模拟器时,不允许驱动程序修改driver_entry。
如果将驱动程序编译为静态包含,则通过--enable-static-drivers
,您必须定义STATIC_ERLANG_DRIVER
在DRIVER_INIT
申报。
注
不要声明driver_entry常量。 这是因为模拟器必须修改句柄和handle2字段。 静态分配的和常量声明的driver_entry可以位于只读内存中,这会导致模拟器崩溃。
数据类型
ErlDrvEntry
typedef struct erl_drv_entry {
int (*init)(void /* Called at system startup for statically
linked drivers, and after loading for
dynamically loaded drivers */
#ifndef ERL_SYS_DRV
ErlDrvData (*start)(ErlDrvPort port, char *command
/* Called when open_port/2 is invoked,
return value -1 means failure */
#else
ErlDrvData (*start)(ErlDrvPort port, char *command, SysDriverOpts* opts
/* Special options, only for system driver */
#endif
void (*stop)(ErlDrvData drv_data
/* Called when port is closed, and when the
emulator is halted */
void (*output)(ErlDrvData drv_data, char *buf, ErlDrvSizeT len
/* Called when we have output from Erlang to
the port */
void (*ready_input)(ErlDrvData drv_data, ErlDrvEvent event
/* Called when we have input from one of
the driver's handles */
void (*ready_output)(ErlDrvData drv_data, ErlDrvEvent event
/* Called when output is possible to one of
the driver's handles */
char *driver_name; /* Name supplied as command in
erlang:open_port/2 */
void (*finish)(void /* Called before unloading the driver -
dynamic drivers only */
void *handle; /* Reserved, used by emulator internally */
ErlDrvSSizeT (*control)(ErlDrvData drv_data, unsigned int command,
char *buf, ErlDrvSizeT len,
char **rbuf, ErlDrvSizeT rlen
/* "ioctl" for drivers - invoked by
port_control/3 */
void (*timeout)(ErlDrvData drv_data
/* Handling of time-out in driver */
void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev
/* Called when we have output from Erlang
to the port */
void (*ready_async)(ErlDrvData drv_data, ErlDrvThreadData thread_data
void (*flush)(ErlDrvData drv_data
/* Called when the port is about to be
closed, and there is data in the
driver queue that must be flushed
before 'stop' can be called */
ErlDrvSSizeT (*call)(ErlDrvData drv_data, unsigned int command,
char *buf, ErlDrvSizeT len,
char **rbuf, ErlDrvSizeT rlen, unsigned int *flags
/* Works mostly like 'control', a synchronous
call into the driver */
void (*event)(ErlDrvData drv_data, ErlDrvEvent event,
ErlDrvEventData event_data
/* Called when an event selected by
driver_event() has occurred */
int extended_marker; /* ERL_DRV_EXTENDED_MARKER */
int major_version; /* ERL_DRV_EXTENDED_MAJOR_VERSION */
int minor_version; /* ERL_DRV_EXTENDED_MINOR_VERSION */
int driver_flags; /* ERL_DRV_FLAGs */
void *handle2; /* Reserved, used by emulator internally */
void (*process_exit)(ErlDrvData drv_data, ErlDrvMonitor *monitor
/* Called when a process monitor fires */
void (*stop_select)(ErlDrvEvent event, void* reserved
/* Called to close an event object */
} ErlDrvEntry;
int (*init)(void)
在驱动程序被erl_ddll:load_driver / 2加载后(实际上当驱动程序被添加到驱动程序列表中时)直接调用。 驱动程序返回0,或者如果驱动程序无法初始化,则返回-1。
ErlDrvData (*start)(ErlDrvPort port, char* command)
当驱动程序被实例化时,调用erlang:open_port / 2时调用。 驱动程序将返回数字> = 0或指针,或者,如果驱动程序无法启动,则返回三个错误代码之一:
ERL_DRV_ERROR_GENERAL
一般错误,没有错误代码ERL_DRV_ERROR_ERRNO
错误代码的错误。errnoERL_DRV_ERROR_BADARG
错误,badarg
如果返回错误代码,则端口未启动。
void (*stop)(ErlDrvData drv_data)
当端口关闭时调用,使用erlang:port_close / 1或Port! {self(),close}。 注意,终止端口所有者进程也会关闭端口。 如果drv_data是一个指向开始分配的内存的指针,那么stop是释放该内存的地方。
void (*output)(ErlDrvData drv_data, char *buf, ErlDrvSizeT len)
Erlang进程向端口发送数据时调用。 数据由buf指向,并且是len个字节。 数据通过端口发送到端口! {self(),{command,Data}}或erlang:port_command / 2。 根据端口的打开方式,它可能是一个整数列表0 ... 255或二进制。 请参阅erlang:open_port / 2和erlang:port_command / 2。
void (*ready_input)(ErlDrvData drv_data, ErlDrvEvent event)void (*ready_output)(ErlDrvData drv_data, ErlDrvEvent event)
在驱动器事件(在参数中指定event
)发送信号时调用。这用于帮助异步驱动程序在出现问题时“唤醒”。
在Unix上,事件是一个管道或套接字句柄(或选择系统调用可理解的东西)。
在Windows上,事件是事件或信号量(或WaitForMultipleObjects API函数可理解的事情)。 (模拟器中的一些技巧允许使用超过64个事件的内置限制。)
要在线程和异步例程中使用它,请在Unix上创建管道,在Windows上创建事件。 例程完成后,写入管道(在Windows上使用SetEvent),这会使模拟器调用ready_input或ready_output。
假事件可能发生。 也就是说,调用ready_input或ready_output,尽管没有实际的事件发出信号。 事实上,这是罕见的(和操作系统相关),但一个强大的驱动程序必须能够处理这种情况。
char *driver_name
驱动程序名称。它必须对应于erlang:open_port/2
驱动程序库文件中使用的原子和驱动程序库文件的名称(不带扩展名)。
void (*finish)(void)
erl_ddll
卸载驱动程序时由驱动程序调用。(它只在动态驱动程序中调用。)
驱动程序仅作为调用的结果卸载erl_ddll:unload_driver/1
,或者在仿真器暂停时卸载。
void *handle
该字段保留供仿真器内部使用。模拟器将修改这个字段,所以重要的driver_entry
是没有声明const
。
ErlDrvSSizeT(控制)(ErlDrvData drv_data,unsigned int命令,char buf,ErlDrvSizeT len,char
rbuf,ErlDrvSizeT rlen)**
用erlang:port_control / 3调用一个特殊的例程。 它对Erlang驱动程序有点像“ioctl”。 指定给port_control / 3的数据以buf和len的形式到达。 驱动程序可以使用* rbuf和rlen发回数据。
这是调用驱动程序并获得响应的最快方式。 它不会在Erlang模拟器中进行上下文切换,也不需要消息传递。 当Erlang太慢时,它适合调用C函数以获得更快的执行速度。
如果驱动程序想要返回数据,则将其返回到rbuf中。 当调用控件时,* rbuf指向一个默认的rlen字节缓冲区,它可以用来返回数据。 根据端口控制标志(使用erl_driver:set_port_control_flags设置的那些标志),数据返回的方式不同。
如果该标志被设置为PORT_CONTROL_FLAG_BINARY,则返回二进制。 通过将原始数据写入默认缓冲区可以返回小的二进制文件。 通过设置* rbuf指向erl_driver:driver_alloc_binary分配的二进制文件,也可以返回二进制文件。 这个二进制在控制返回后自动释放。 驱动程序可以使用erl_driver:driver_binary_inc_refc保留用于只读访问的二进制文件,稍后用erl_driver:driver_free_binary释放。 在控制返回后决不允许更改二进制文件。 如果* rbuf设置为NULL,则返回空列表。
如果该标志设置为0,则数据作为整数列表返回。 可以使用默认缓冲区,也可以设置* rbuf指向使用erl_driver:driver_alloc分配的较大缓冲区。 控制返回后,缓冲区会自动释放。
如果返回的字节超过几个字节,使用二进制文件会更快。
返回值是返回的字节数*rbuf
。
void (*timeout)(ErlDrvData drv_data)
驱动程序的计时器到达后随时调用0
。定时器使用激活erl_driver:driver_set_timer
。驾驶员之间不存在任何优先顺序或排序,因此,如果多个驾驶员同时超时,其中任何一个都会首先被调用。
void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev)
每当写入端口时调用。 如果它是NULL,则调用输出函数。 这个函数比输出快,因为它直接使用ErlIOVec,它不需要复制数据。 端口将处于二进制模式,请参阅erlang:open_port / 2。
ErlIOVec包含适用于writev的SysIOVec和一个或多个二进制文件。 如果驱动程序从outputv返回时将保留这些二进制文件,则可以对它们进行排队(例如,使用erl_driver:driver_enq_bin),或者如果将它们保存在静态变量或全局变量中,则可以递增参考计数器。
void (*ready_async)(ErlDrvData drv_data, ErlDrvThreadData thread_data)
异步调用完成后调用。 异步调用使用erl_driver:driver_async启动。 这个函数是从Erlang模拟器线程调用的,而不是在某个线程中调用的异步函数(如果启用了多线程)。
void (*flush)(ErlDrvData drv_data)
当端口即将关闭时调用,并且在调用“stop”之前必须刷新驱动程序队列中的数据。
`ErlDrvSSizeT (call)(ErlDrvData drv_data, unsigned int command, char buf, ErlDrvSizeT len, char
rbuf, ErlDrvSizeT rlen, unsigned int *flags)`**
从erlang调用:port_call / 3。 它很像控制回调,但是使用外部术语格式进行输入和输出。
command
是一个整数,从Erlang的调用(第二个参数erlang:port_call/3
)中获得。
buf和len为调用提供参数(erlang:port_call / 3的第三个参数)。 它们可以使用ei函数进行解码。
rbuf指向一个返回缓冲区,长度为rlen字节。 返回数据在外部(二进制)格式中是一个有效的Erlang术语。 这将转换为Erlang术语,并由erlang:port_call / 3返回给调用者。 如果需要比rlen字节多的空间来返回数据,可以将* rbuf设置为使用erl_driver:driver_alloc分配的内存。 呼叫返回后,该内存将自动释放。
返回值是* rbuf中返回的字节数。 如果返回ERL_DRV_ERROR_GENERAL(或者事实上,任何<0),erlang:port_call / 3将抛出一个BAD_ARG。
void (*event)(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data)
故意留下无证件。
int extended_marker
此字段要么等于ERL_DRV_EXTENDED_MARKER或0.旧的驱动程序(不知道扩展的驱动程序接口)是将此字段设置为0.如果此字段为0,则以下所有字段也必须为0,否则为NULL 它是一个指针字段。
int major_version
如果字段extended_marker等于ERL_DRV_EXTENDED_MARKER,则该字段等于ERL_DRV_EXTENDED_MAJOR_VERSION。
int minor_version
如果字段extended_marker等于ERL_DRV_EXTENDED_MARKER,则该字段等于ERL_DRV_EXTENDED_MINOR_VERSION。
int driver_flags
该字段用于将驱动程序功能和其他信息传递给运行系统。 如果字段extended_marker等于ERL_DRV_EXTENDED_MARKER,它将包含0或驱动器标志(ERL_DRV_FLAG_ *)按位或运算。 以下驱动程序标志存在:
ERL_DRV_FLAG_USE_PORT_LOCKING
当驱动程序在具有SMP支持的运行时系统中运行时,运行时系统在执行此驱动程序的所有端口上使用端口级锁定,而不是驱动程序级锁定。 有关更多信息,请参阅erl_driver。
ERL_DRV_FLAG_SOFT_BUSY
标记驱动程序实例可以处理在输出和/或outputv回调中被调用,尽管驱动程序实例已将其标记为繁忙(请参阅erl_driver:set_busy_port)。 从ERTS 5.7.4开始,Erlang发行版使用的驱动程序需要此标志(发行版所使用的驱动程序一直需要该行为)。
ERL_DRV_FLAG_NO_BUSY_MSGQ
禁用繁忙的端口消息队列功能。有关更多信息,请参阅erl_driver:erl_drv_busy_msgq_limits
。
ERL_DRV_FLAG_USE_INIT_ACK
指定此标志时,链接驱动程序必须手动确认端口已成功地使用erl_driver:erl_drv_init_ack()
这允许实现者将erlang:open_port
用badarg
在完成了一些初始的异步初始化之后。
void *handle2
该字段保留供仿真器内部使用。模拟器修改这个字段,所以重要的driver_entry
是没有声明const
。
void (*process_exit)(ErlDrvData drv_data, ErlDrvMonitor *monitor)
被监控进程退出时调用。 drv_data是与监视进程的端口(使用erl_driver:driver_monitor_process)相关联的数据,监视器与创建监视器时填充的ErlDrvMonitor结构相对应。 驱动程序接口函数erl_driver:driver_get_monitored_process可用于以ErlDrvTermData方式检索退出进程的进程ID。
void (*stop_select)(ErlDrvEvent event, void* reserved)
当可以安全地关闭事件对象时,代表erl_driver:driver_select调用。
在unix上的一个典型实现是close((int)event)
...
争论reserved
是为未来使用而设,应予以忽略。
与大多数其他回调函数相反,stop_select
被称为独立于任何端口。没有ErlDrvData
参数传递给函数。不保证驱动程序锁定或端口锁定。被调用的端口driver_select
甚至可以在当时被关闭stop_select
。但stop_select
是直接调用的情况erl_driver:driver_select
。
不允许从stop_select调用驱动程序API中的任何函数。 这种严格的限制是因为可以调用stop_select的volatile变量。
另见
erl_driver(3)
,,,erlang(3)
,,,erl_ddll(3)