查看源代码 erl_driver
Erlang 驱动程序的 API 函数。
描述
Erlang 驱动程序是一个库,其中包含一组原生驱动程序回调函数,当某些事件发生时,Erlang 虚拟机将调用这些函数。一个驱动程序可以有多个实例,每个实例都与一个 Erlang 端口关联。
警告
请极其谨慎地使用此功能。
驱动程序回调作为虚拟机原生代码的直接扩展执行。执行不在安全环境中进行。虚拟机无法提供与执行 Erlang 代码时相同的服务,例如抢占式调度或内存保护。如果驱动程序回调函数行为不正常,整个虚拟机将发生故障。
- 驱动程序回调崩溃将导致整个虚拟机崩溃。
- 错误实现的驱动程序回调可能会导致虚拟机内部状态不一致,这可能会导致虚拟机崩溃,或者在调用驱动程序回调之后的任何时间点出现虚拟机的各种错误行为。
- 在返回之前执行长时间工作的驱动程序回调会降低虚拟机的响应速度,并可能导致各种奇怪的行为。此类奇怪的行为包括但不限于,极端的内存使用以及调度器之间的不良负载平衡。由于长时间工作而可能发生的奇怪行为在 Erlang/OTP 版本之间也可能有所不同。
从 ERTS 5.5.3 开始,驱动程序接口已扩展(请参阅 extended marker
)。扩展接口引入了版本管理,在驱动程序初始化时将功能标志(请参阅 driver_flags
)传递给运行时系统的可能性,以及一些新的驱动程序 API 函数。
注意
从 ERTS 5.9 开始,旧驱动程序必须重新编译并使用扩展接口。它们还必须调整为支持 64 位的驱动程序接口。
驱动程序使用在 erl_driver.h
中声明的 API 函数回调到模拟器。它们用于从驱动程序输出数据、使用定时器等等。
每个驱动程序实例都与一个端口关联。每个端口都有一个端口所有者进程。与端口的通信通常通过端口所有者进程完成。大多数函数都将 port
句柄作为参数。这标识了驱动程序实例。请注意,此端口句柄必须由驱动程序存储,它不是在从模拟器调用驱动程序时给出的(请参阅driver_entry
)。
某些函数采用 ErlDrvBinary
类型的参数,即驱动程序二进制文件。它应由调用方分配和释放。直接使用二进制文件可以避免额外的数据复制。
许多输出函数都有一个“头部缓冲区”,其中包含 hbuf
和 hlen
参数。此缓冲区在发送的二进制文件(或列表,具体取决于端口模式)之前以列表形式发送。当匹配从端口接收的消息时,这很方便。(尽管在最新的 Erlang 版本中,有二进制语法,可以使用该语法匹配二进制文件的开头。)
驱动程序在驱动程序级别或端口级别(驱动程序实例级别)被锁定。默认情况下,将使用驱动程序级别锁定,也就是说,一次只有一个模拟器线程会在驱动程序中执行代码。如果使用端口级别锁定,则多个模拟器线程可以同时在驱动程序中执行代码。但是,一次只有一个线程会调用与同一端口对应的驱动程序回调。要启用端口级别锁定,请在驱动程序使用的driver_entry
中设置 ERL_DRV_FLAG_USE_PORT_LOCKING
驱动程序标志。当使用端口级别锁定时,驱动程序编写者负责同步对端口(驱动程序实例)共享的所有数据的访问。
如果使用驱动程序级别锁定,则大多数在存在 SMP 支持的运行时系统之前编写的驱动程序都可以在具有 SMP 支持的运行时系统中运行,而无需重写。
注意
假设驱动程序不访问其他驱动程序。如果驱动程序相互访问,则它们必须提供自己的线程安全同步机制。强烈建议不要进行此类“驱动程序间通信”。
注意
无论使用哪种锁定方案,都可以从不同的线程调用驱动程序回调。
此 API 中的大多数函数都不是线程安全的,也就是说,它们不能从任意线程调用。未记录为线程安全的函数只能从驱动程序回调或从驱动程序回调调用派生的函数调用中调用。请注意,可以从不同的线程调用驱动程序回调。但是,对于此 API 中的任何函数,这都不是问题,因为模拟器可以控制这些线程。
警告
未明确记录为线程安全的函数不是线程安全的。
未明确记录为线程安全的函数在运行时系统的某个时间点可能具有线程安全的实现。但是,这样的实现可以随时在不发出任何通知的情况下更改为线程不安全的实现。
仅从任意线程使用明确记录为线程安全的函数。
如本节开头的警告文本中所述,驱动程序回调相对较快地返回至关重要。很难给出允许驱动程序回调工作的时间上限,但通常一个行为良好的驱动程序回调应在 1 毫秒内返回。可以使用不同的方法实现此目的。如果可以完全控制在驱动程序回调中执行的代码,则最佳方法是将工作划分为多个工作块,并使用零超时触发对超时回调的多次调用。函数 erl_drv_consume_timeslice
可用于确定何时触发此类超时回调调用。但是,有时无法以这种方式实现,例如,当调用第三方库时。在这种情况下,通常需要将工作调度到另一个线程。下面提供了有关线程原语的信息。
功能
驱动程序需要使用 Erlang 执行的所有操作都是通过驱动程序 API 函数完成的。存在以下功能的函数
定时器函数 - 控制驱动程序可以使用的定时器。定时器使模拟器在指定时间后调用
timeout
入口函数。每个驱动程序实例只有一个定时器可用。队列处理 - 每个驱动程序实例都有一个关联的队列。此队列是一个
SysIOVec
,充当缓冲区。它主要用于驱动程序缓冲要写入设备的数据,它是一个字节流。如果端口所有者进程关闭驱动程序,并且队列不为空,则驱动程序不会关闭。这使驱动程序可以在关闭之前刷新其缓冲区。如果使用端口数据锁定,则可以从任何线程操作队列。有关更多信息,请参阅
ErlDrvPDL
。输出函数 - 通过这些函数,驱动程序将数据发送回模拟器。数据由端口所有者进程作为消息接收,请参阅
erlang:open_port/2
。向量函数和采用驱动程序二进制文件的函数速度更快,因为它们避免了复制数据缓冲区。还有一种从驱动程序发送术语的快速方法,而无需使用二进制术语格式。失败 - 驱动程序可以退出并将错误信号发送到 Erlang。这仅适用于严重错误,当驱动程序不可能保持打开时。
异步调用 - Erlang/OTP R7B 及更高版本提供了异步函数调用的功能,使用 Erlang 提供的线程池。还有一个选择调用,可用于异步驱动程序。
多线程
提供了一个用于多线程的类 POSIX 线程 API。Erlang 驱动程序线程 API 仅提供 POSIX 线程 API 提供功能的一个子集。提供的子集或多或少是多线程编程所需的基本功能Erlang 驱动程序线程 API 可以与 UN-ices 上的 POSIX 线程 API 以及 Windows 上的 Windows 本机线程 API 结合使用。Erlang 驱动程序线程 API 的优点是可移植,但可能存在希望使用 POSIX 线程 API 或 Windows 本机线程 API 的功能的情况。
仅当从错误条件恢复是合理时,Erlang 驱动程序线程 API 才会返回错误代码。如果从错误条件恢复不合理,则会终止整个运行时系统。例如,如果创建互斥锁操作失败,则会返回错误代码,但如果互斥锁上的锁定操作失败,则会终止整个运行时系统。
请注意,Erlang 驱动程序线程 API 中没有“具有超时的条件变量等待”。这是因为
pthread_cond_timedwait
的问题。当系统时钟突然更改时,不能总是保证您会按预期从调用中唤醒。Erlang 运行时系统必须能够应对系统时钟的突然更改。因此,我们已将其从 Erlang 驱动程序线程 API 中省略。在 Erlang 驱动程序的情况下,超时可以使用 Erlang 驱动程序 API 的定时器功能来处理。请注意,Erlang 驱动程序 API 中的许多函数不是线程安全的。如果未记录函数为线程安全的,则它不是线程安全的。
注意
在模拟器线程中执行时,非常重要的是,在让线程失去控制之前解锁您已锁定的所有锁;否则,您很可能会使整个模拟器死锁。
如果需要在模拟器线程中使用线程特定数据,请仅在线程受您控制时设置线程特定数据,并在让线程失去控制之前清除线程特定数据。
将来,调试功能可能会与 Erlang 驱动程序线程 API 集成。所有创建实体的函数都采用
name
参数。当前,name
参数未使用,但它将在实现调试功能时使用。如果正确命名所有创建的实体,则调试功能将能够为您提供更好的错误报告。添加/删除驱动程序 - 驱动程序可以添加和稍后删除驱动程序。
监视进程 - 驱动程序可以监视不拥有端口的进程。
版本管理
对于将其extended_marker
字段的driver_entry
设置为ERL_DRV_EXTENDED_MARKER
的驱动程序,将启用版本管理。erl_driver.h
定义ERL_DRV_EXTENDED_MARKER
ERL_DRV_EXTENDED_MAJOR_VERSION
,当 Erlang 运行时系统发生驱动程序不兼容的更改时,此值会递增。通常,当ERL_DRV_EXTENDED_MAJOR_VERSION
发生更改时,重新编译驱动程序就足够了,但在极少数情况下,可能意味着驱动程序必须进行少量修改。如果是这种情况,当然会进行文档记录。ERL_DRV_EXTENDED_MINOR_VERSION
,当添加新功能时,此值会递增。运行时系统使用驱动程序的次要版本来确定要使用的功能。
通常,如果主要版本不同,或者如果主要版本相同但驱动程序使用的次要版本大于运行时系统使用的次要版本,则运行时系统会拒绝加载驱动程序。然而,在主要版本升级后的两个主要版本过渡期内,允许使用较低主要版本的旧驱动程序。但是,如果使用了已弃用的功能,这些旧驱动程序可能会失败。
模拟器会拒绝加载不使用扩展驱动程序接口的驱动程序,以便支持 64 位驱动程序,因为在 Erlang/OTP R15B 中,回调
output
、control
和call
的类型发生了不兼容的更改。使用旧类型编写的驱动程序在编译时会产生警告,并且在调用时会向模拟器返回垃圾大小,导致它读取随机内存并创建巨大的不正确结果 Blob。因此,仅仅重新编译使用 pre R15B 类型进行版本管理的驱动程序是不够的;必须更改驱动程序中的类型,这表明需要进行其他重写,尤其是在大小变量方面。重新编译时,请检查所有警告。
此外,API 驱动程序函数
driver_output*
和driver_vec_to_buf
、driver_alloc/realloc*
以及driver_*
队列函数已更改为具有更大的长度参数和返回值。这是一个较小的问题,因为传递较小类型的代码会在调用中自动转换它们,并且只要驱动程序不处理溢出int
的大小,一切都将像以前一样工作。
64 位驱动程序接口的重写
ERTS 5.9 引入了两个新的整数类型,ErlDrvSizeT
和 ErlDrvSSizeT
,如果需要,它们可以容纳 64 位大小。
为了不更新驱动程序而只重新编译,当为 32 位机器构建时,它可能会工作,从而产生一种虚假的安全感。希望这会产生许多重要的警告。但是,当稍后为 64 位机器重新编译同一个驱动程序时,将会出现警告,并且几乎肯定会崩溃。因此,推迟更新驱动程序并且不修复警告是一个糟糕的主意。
使用 gcc
重新编译时,请使用标志 -Wstrict-prototypes
以获得更好的警告。如果您使用其他编译器,请尝试找到类似的标志。
以下是重写 pre ERTS 5.9 驱动程序的清单,最重要的排在最前面
驱动程序回调的返回类型 - 重写驱动程序回调
control
以使用返回类型ErlDrvSSizeT
而不是int
。重写驱动程序回调
call
以使用返回类型ErlDrvSSizeT
而不是int
。注意
这些更改对于避免模拟器崩溃或更糟糕的情况(导致故障)至关重要。如果没有这些更改,驱动程序可能会向模拟器返回高 32 位中的垃圾,导致它从随机字节构建一个巨大的结果,要么在内存分配时崩溃,要么从驱动程序调用中成功获得一个随机结果。
驱动程序回调的参数 - 驱动程序回调
output
现在将ErlDrvSizeT
作为第 3 个参数,而不是之前的int
。驱动程序回调
control
现在将ErlDrvSizeT
作为第 4 个和第 6 个参数,而不是之前的int
。驱动程序回调
call
现在将ErlDrvSizeT
作为第 4 个和第 6 个参数,而不是之前的int
。健全的编译器调用约定可能使得这些更改仅对于需要 64 位大小字段(主要是大于 2 GB,因为这是 32 位
int
可以容纳的)来处理数据块的驱动程序才是必要的。但是,可以想到非健全的调用约定,这些约定可能会使驱动程序回调混淆参数,从而导致故障。注意
参数类型更改是从有符号到无符号。如果您只是在各处更改类型,则这可能会导致问题,例如循环终止条件或错误条件。
ErlIOVec
中更大的size
字段 -ErlIOVec
中的size
字段已从int
更改为ErlDrvSizeT
。检查所有使用该字段的代码。自动类型转换可能使得这些更改仅对于遇到大于 32 位大小的驱动程序才是必要的。
注意
size
字段从有符号更改为无符号。如果您只是在各处更改类型,则这可能会导致问题,例如循环终止条件或错误条件。驱动程序 API 中的参数和返回值 - 许多驱动程序 API 函数已将参数类型和/或返回值从大多数
int
更改为ErlDrvSizeT
。自动类型转换可能使得这些更改仅对于遇到大于 32 位大小的驱动程序才是必要的。driver_output
- 第 3 个参数driver_output2
- 第 3 个和第 5 个参数driver_output_binary
- 第 3 个、第 5 个和第 6 个参数driver_outputv
- 第 3 个和第 5 个参数driver_vec_to_buf
- 第 3 个参数和返回值driver_alloc
- 第 1 个参数driver_realloc
- 第 2 个参数driver_alloc_binary
- 第 1 个参数driver_realloc_binary
- 第 2 个参数driver_enq
- 第 3 个参数driver_pushq
- 第 3 个参数driver_deq
- 第 2 个参数和返回值driver_sizeq
- 返回值driver_enq_bin
- 第 3 个和第 4 个参数driver_pushq_bin
- 第 3 个和第 4 个参数driver_enqv
- 第 3 个参数driver_pushqv
- 第 3 个参数driver_peekqv
- 返回值
注意
这是从有符号到无符号的更改。如果您只是在各处更改类型,则这可能会导致问题,例如循环终止条件和错误条件。
数据类型
ErlDrvSizeT
- 用作size_t
的无符号整数类型。ErlDrvSSizeT
- 有符号整数类型,大小与ErlDrvSizeT
相同。ErlDrvSysInfo
typedef struct ErlDrvSysInfo { int driver_major_version; int driver_minor_version; char *erts_version; char *otp_release; int thread_support; int smp_support; int async_threads; int scheduler_threads; int nif_major_version; int nif_minor_version; int dirty_scheduler_support; } ErlDrvSysInfo;
ErlDrvSysInfo
结构用于存储有关 Erlang 运行时系统的信息。driver_system_info
在传递对ErlDrvSysInfo
结构的引用时写入系统信息。结构中的字段如下driver_major_version
- 编译运行时系统时ERL_DRV_EXTENDED_MAJOR_VERSION
的值。此值与编译驱动程序时使用的ERL_DRV_EXTENDED_MAJOR_VERSION
的值相同;否则,运行时系统会拒绝加载驱动程序。driver_minor_version
- 编译运行时系统时ERL_DRV_EXTENDED_MINOR_VERSION
的值。此值可能与编译驱动程序时使用的ERL_DRV_EXTENDED_MINOR_VERSION
的值不同。erts_version
- 包含运行时系统版本号的字符串(与erlang:system_info(version)
返回的相同)。otp_release
- 包含 OTP 发布号的字符串(与erlang:system_info(otp_release)
返回的相同)。thread_support
- 如果运行时系统具有线程支持,则值为!= 0
;否则为0
。smp_support
- 如果运行时系统具有 SMP 支持,则值为!= 0
;否则为0
。async_threads
-driver_async
使用的异步线程池中的异步线程数(与erlang:system_info(thread_pool_size)
返回的相同)。scheduler_threads
- 运行时系统使用的调度器线程数(与erlang:system_info(schedulers)
返回的相同)。nif_major_version
- 编译运行时系统时ERL_NIF_MAJOR_VERSION
的值。nif_minor_version
- 编译运行时系统时ERL_NIF_MINOR_VERSION
的值。dirty_scheduler_support
- 如果运行时系统支持脏调度器线程,则值为!= 0
;否则为0
。
ErlDrvBinary
typedef struct ErlDrvBinary { ErlDrvSint orig_size; char orig_bytes[]; } ErlDrvBinary;
ErlDrvBinary
结构是一个二进制数据,在仿真器和驱动程序之间发送。所有二进制数据都采用引用计数;当调用driver_binary_free
时,引用计数会递减,当引用计数达到零时,二进制数据会被释放。orig_size
是二进制数据的大小,orig_bytes
是缓冲区。ErlDrvBinary
的大小不是固定的,它的大小为orig_size + 2 * sizeof(int)
。注意
已经移除了
refc
字段。ErlDrvBinary
的引用计数现在存储在其他地方。可以通过driver_binary_get_refc
、driver_binary_inc_refc
和driver_binary_dec_refc
来访问ErlDrvBinary
的引用计数。一些驱动程序调用(例如
driver_enq_binary
)会递增驱动程序的引用计数,而另一些调用(例如driver_deq
)会递减引用计数。使用驱动程序二进制数据而不是普通缓冲区通常更快,因为仿真器不需要复制数据,只需要使用指针。
在驱动程序中使用
driver_alloc_binary
分配的驱动程序二进制数据,需要在驱动程序中使用driver_free_binary
释放(除非另有说明)。(请注意,如果仿真器中仍然引用了该驱动程序,则引用计数不会变为零,因此不一定会释放它。)驱动程序二进制数据用于
driver_output2
和driver_outputv
调用以及队列中。此外,驱动程序回调outputv
也使用驱动程序二进制数据。如果驱动程序由于某种原因想要保留驱动程序二进制数据,例如在静态变量中,则需要递增引用计数,并且可以在
stop
回调中使用driver_free_binary
释放二进制数据。请注意,驱动程序二进制数据由驱动程序和仿真器共享。驱动程序不得更改从仿真器接收或发送到仿真器的二进制数据。
自 ERTS 5.5 (Erlang/OTP R11B) 起,保证
orig_bytes
针对双精度数组的存储进行了正确对齐(通常为 8 字节对齐)。ErlDrvData
- 驱动程序特定数据的句柄,传递给驱动程序回调。它是一个指针,通常在驱动程序中强制转换为特定类型的指针。SysIOVec
- 系统 I/O 向量,如 Unix 中的writev
和 Win32 中的WSASend
所使用。它在ErlIOVec
中使用。ErlIOVec
typedef struct ErlIOVec { int vsize; ErlDrvSizeT size; SysIOVec* iov; ErlDrvBinary** binv; } ErlIOVec;
仿真器和驱动程序使用的 I/O 向量是二进制数据列表,其中
SysIOVec
指向二进制数据的缓冲区。它在driver_outputv
和outputv
驱动程序回调中使用。此外,驱动程序队列也是一个ErlIOVec
。ErlDrvMonitor
- 当驱动程序为进程创建监视器时,会填充ErlDrvMonitor
。这是一个不透明的数据类型,可以赋值,但不能在不使用提供的比较函数的情况下进行比较(即,它的行为类似于一个结构体)。驱动程序编写者需要在调用
driver_monitor_process
时为存储监视器提供内存。数据的地址不会存储在驱动程序之外,因此ErlDrvMonitor
可以像任何其他数据一样使用,可以复制、在内存中移动、遗忘等等。ErlDrvNowData
-ErlDrvNowData
结构包含一个时间戳,该时间戳由从过去某个任意点测量的三个值组成。这三个结构成员是megasecs
- 自任意时间点以来经过的整兆秒数secs
- 自任意时间点以来经过的整秒数microsecs
- 自任意时间点以来经过的整微秒数
ErlDrvPDL
- 如果必须从调用驱动程序回调的其他线程访问某些特定于端口的数据,则可以使用端口数据锁来同步对数据的操作。当前,仿真器与端口数据锁关联的唯一特定于端口的数据是驱动程序队列。通常,驱动程序实例没有端口数据锁。如果驱动程序实例想要使用端口数据锁,则必须通过调用
driver_pdl_create
来创建端口数据锁。注意
一旦创建了端口数据锁,则在端口数据锁被锁定时,必须执行对与端口数据锁关联的任何数据的每次访问。端口数据锁分别通过
driver_pdl_lock
和driver_pdl_unlock
来锁定和解锁。端口数据锁采用引用计数,当引用计数达到零时,它将被销毁。当创建锁时,仿真器至少递增一次引用计数,并且当与锁关联的端口终止时,仿真器会递减一次引用计数。当异步作业入队时,仿真器也会递增引用计数,当异步作业被调用时,仿真器会递减引用计数。此外,驱动程序负责确保引用计数在驱动程序最后一次使用锁之前不会达到零。可以通过
driver_pdl_get_refc
、driver_pdl_inc_refc
和driver_pdl_dec_refc
分别读取、递增和递减引用计数。ErlDrvTid
- 线程标识符。另请参阅
erl_drv_thread_create
、erl_drv_thread_exit
、erl_drv_thread_join
、erl_drv_thread_self
和erl_drv_equal_tids
。ErlDrvThreadOpts
int suggested_stack_size;
传递给
erl_drv_thread_create
的线程选项结构。存在以下字段suggested_stack_size
- 关于要使用的堆栈大小的建议,以千字为单位。值 < 0 表示默认大小。
另请参阅
erl_drv_thread_opts_create
、erl_drv_thread_opts_destroy
和erl_drv_thread_create
。ErlDrvMutex
- 互斥锁。用于同步对共享数据的访问。一次只能有一个线程锁定互斥锁。另请参阅
erl_drv_mutex_create
、erl_drv_mutex_destroy
、erl_drv_mutex_lock
、erl_drv_mutex_trylock
和erl_drv_mutex_unlock
。ErlDrvCond
- 条件变量。当线程必须等待特定条件出现才能继续执行时使用。条件变量必须与关联的互斥锁一起使用。另请参阅
erl_drv_cond_create
、erl_drv_cond_destroy
、erl_drv_cond_signal
、erl_drv_cond_broadcast
和erl_drv_cond_wait
。ErlDrvRWLock
- 读/写锁。用于允许多个线程读取共享数据,同时只允许一个线程写入相同的数据。多个线程可以同时读取锁定读写锁,而一次只能有一个线程读取/写入锁定读写锁。另请参阅
erl_drv_rwlock_create
、erl_drv_rwlock_destroy
、erl_drv_rwlock_rlock
、erl_drv_rwlock_tryrlock
、erl_drv_rwlock_runlock
、erl_drv_rwlock_rwlock
、erl_drv_rwlock_tryrwlock
和erl_drv_rwlock_rwunlock
。ErlDrvTSDKey
- 线程特定数据可以与之关联的键。另请参阅
erl_drv_tsd_key_create
、erl_drv_tsd_key_destroy
、erl_drv_tsd_set
和erl_drv_tsd_get
。ErlDrvTime
- 用于时间表示的有符号 64 位整数类型。ErlDrvTimeUnit
- 驱动程序 API 支持的时间单位枚举ERL_DRV_SEC
- 秒ERL_DRV_MSEC
- 毫秒ERL_DRV_USEC
- 微秒ERL_DRV_NSEC
- 纳秒
add_driver_entry()
void add_driver_entry(ErlDrvEntry
*de);
将驱动程序条目添加到 Erlang 已知的驱动程序列表中。会调用参数 de
的 init
函数。
注意
使用此函数添加驻留在动态加载代码中的驱动程序是危险的。如果所添加驱动程序的驱动程序代码与正常的动态加载驱动程序(使用
erl_ddll
接口加载)驻留在同一个动态加载模块(即.so
文件)中,则调用者必须在添加驱动程序条目之前调用driver_lock_driver
。通常不建议使用此函数。
driver_alloc()
void * driver_alloc(ErlDrvSizeT size);
分配大小由 size
指定的内存块,并返回它。仅在内存不足时失败,此时返回 NULL
。(这通常是 malloc
的包装器)。
分配的内存必须通过相应地调用 driver_free
显式释放(除非另有说明)。
此函数是线程安全的。
driver_alloc_binary()
ErlDrvBinary * driver_alloc_binary(ErlDrvSizeT size);
分配一个驱动程序二进制文件,其内存块至少为 size
字节,并返回指向它的指针,如果失败(内存不足)则返回 NULL
。当驱动程序二进制文件被发送到仿真器后,不能再更改它。每个已分配的二进制文件都必须通过相应地调用 driver_free_binary
来释放(除非另有说明)。
请注意,驱动程序二进制文件有一个内部引用计数器。这意味着调用 driver_free_binary
可能实际上不会释放它。如果它被发送到仿真器,则可以在那里被引用。
驱动程序二进制文件有一个字段 orig_bytes
,它标记了二进制文件中数据的起始位置。
此函数是线程安全的。
driver_async()
long driver_async(ErlDrvPort port, unsigned
int* key, void (*async_invoke)(void*), void* async_data, void
(*async_free)(void*));
执行异步调用。函数 async_invoke
在与仿真器线程分离的线程中调用。这使驱动程序能够执行耗时、阻塞的操作,而不会阻塞仿真器。
异步线程池大小可以使用命令行参数 +A
在 erl(1)
中设置。如果异步线程池不可用,则会在调用 driver_async
的线程中同步进行调用。异步线程池中当前异步线程的数量可以通过 driver_system_info
获取。
如果线程池可用,则会使用一个线程。如果参数 key
为 NULL
,则会以循环方式使用池中的线程,每次调用 driver_async
都会使用池中的下一个线程。设置参数 key
后,此行为将更改。 *key
的两个相同值始终会获得同一个线程。
为了确保驱动程序实例始终使用同一个线程,可以使用以下调用
unsigned int myKey = driver_async_port_key(myPort);
r = driver_async(myPort, &myKey, myData, myFunc);
对于每个驱动程序实例,初始化 myKey
一次就足够了。
如果线程已经在工作,则调用将被排队并按顺序执行。对每个驱动程序实例使用同一个线程可确保按顺序进行调用。
async_data
是函数 async_invoke
和 async_free
的参数。它通常是指向结构的指针,该结构包含一个管道或事件,可用于发出异步操作已完成的信号。数据将在 async_free
中释放。
当异步操作完成时,将调用 ready_async
驱动程序条目函数。如果驱动程序条目中的 ready_async
为 NULL
,则会改为调用 async_free
函数。
如果 driver_async
调用失败,则返回值是 -1
。
注意
从 ERTS 5.5.4.3 开始,异步线程池中线程的默认堆栈大小为 16 千字,即在 32 位架构上为 64 千字节。之所以选择这个较小的默认大小,是因为异步线程的数量可能非常大。默认堆栈大小对于随 Erlang/OTP 交付的驱动程序来说足够了,但对于使用
driver_async
功能的其他动态链接的驱动程序来说,可能不够大。异步线程池中线程的建议堆栈大小可以通过命令行参数+a
在erl(1)
中配置。
driver_async_port_key()
unsigned int driver_async_port_key(ErlDrvPort port);
计算一个键,以便稍后在 driver_async
中使用。这些键是均匀分布的,以便实现端口 ID 和异步线程 ID 之间的公平映射。
注意
在 Erlang/OTP R16 之前,可以使用端口 ID 进行适当的类型转换来作为键,但在重写端口子系统之后,情况不再如此。通过此函数,您可以实现与 Erlang/OTP R16 之前基于端口 ID 的相同分布。
自 OTP R16B02 起可用
driver_binary_dec_refc()
long driver_binary_dec_refc(ErlDrvBinary *bin);
递减 bin
上的引用计数,并返回递减后达到的引用计数。
此函数是线程安全的。
注意
驱动程序二进制文件的引用计数通常要通过调用
driver_free_binary
来递减。如果引用计数达到零,
driver_binary_dec_refc
不会释放二进制文件。仅当您确定不会达到零的引用计数时,才使用driver_binary_dec_refc
。
driver_binary_get_refc()
long driver_binary_get_refc(ErlDrvBinary *bin);
返回 bin
上的当前引用计数。
此函数是线程安全的。
driver_binary_inc_refc()
long driver_binary_inc_refc(ErlDrvBinary *bin);
递增 bin
上的引用计数,并返回递增后达到的引用计数。
此函数是线程安全的。
driver_caller()
ErlDrvTermData driver_caller(ErlDrvPort
port);
返回当前调用驱动程序的进程的进程 ID。该进程 ID 可以与 driver_send_term
一起使用,将数据发送回调用者。driver_caller
仅在当前在以下驱动程序回调之一中执行时返回有效数据
start
- 从erlang:open_port/2
调用。output
- 从erlang:send/2
和erlang:port_command/2
调用。outputv
- 从erlang:send/2
和erlang:port_command/2
调用。control
- 从erlang:port_control/3
调用。call
- 从erlang:port_call/3
调用。
请注意,此函数是非线程安全的。
driver_cancel_timer()
int driver_cancel_timer(ErlDrvPort port);
取消使用 driver_set_timer
设置的定时器。
返回值为 0
。
driver_compare_monitors()
int driver_compare_monitors(const ErlDrvMonitor
*monitor1, const ErlDrvMonitor *monitor2);
比较两个 ErlDrvMonitor
。也可以用于暗示监视器上的一些人为顺序,无论出于何种原因。
如果 monitor1
和 monitor2
相等,则返回 0
,如果 monitor1
< monitor2
,则返回 < 0
,如果 monitor1
> monitor2
,则返回 > 0
。
driver_connected()
ErlDrvTermData driver_connected(ErlDrvPort
port);
返回端口所有者进程。
请注意,此函数是非线程安全的。
driver_create_port()
ErlDrvPort driver_create_port(ErlDrvPort port,
ErlDrvTermData owner_pid, char* name,
ErlDrvData drv_data);
创建一个新端口,该端口执行与创建新端口的端口相同的驱动程序代码。
port
- 创建新端口的端口(驱动程序实例)的端口句柄。owner_pid
- 将成为新端口所有者的 Erlang 进程的进程 ID。此进程将链接到新端口。您通常希望使用driver_caller(port)
作为owner_pid
。name
- 新端口的端口名称。您通常希望使用与驱动程序名称相同的端口名称(driver_name
driver_entry
的字段)。drv_data
- 驱动程序定义的句柄,在以后调用驱动程序回调时传递。请注意,不会为这个新驱动程序实例调用 驱动程序启动回调。驱动程序定义的句柄通常在通过erlang:open_port/2
创建端口时,在驱动程序启动回调中创建。
允许 driver_create_port
的调用者在 driver_create_port
返回时操作新创建的端口。当使用 端口级锁定时,只允许创建端口在当前驱动程序回调返回之前,操作新创建的端口,该回调由仿真器调用。
driver_demonitor_process()
int driver_demonitor_process(ErlDrvPort port,
const ErlDrvMonitor *monitor);
取消先前创建的监视器。
如果删除了监视器,则返回 0
,如果监视器不再存在,则返回 > 0。
driver_deq()
ErlDrvSizeT driver_deq(ErlDrvPort port,
ErlDrvSizeT size);
通过将驱动程序队列中的头指针向前移动 size
字节来出列数据。队列中的数据会被释放。
如果成功,则返回队列中剩余的字节数,否则返回 -1
。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_enq()
int driver_enq(ErlDrvPort port, char* buf,
ErlDrvSizeT len);
将数据排入驱动程序队列。复制buf
中的数据(len
个字节),并将其放置在驱动程序队列的末尾。驱动程序队列通常以 FIFO 方式使用。
驱动程序队列可用于将来自模拟器的数据排队到驱动程序(从驱动程序到模拟器的数据由模拟器在正常的 Erlang 消息队列中排队)。如果驱动程序必须等待慢速设备等,并希望返回到模拟器,这将很有用。驱动程序队列实现为ErlIOVec
。
当队列包含数据时,驱动程序不会关闭,直到队列为空。
返回值为 0
。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_enq_bin()
int driver_enq_bin(ErlDrvPort port,
ErlDrvBinary *bin, ErlDrvSizeT offset, ErlDrvSizeT len);
将驱动程序二进制文件排入驱动程序队列。将bin
中偏移量为offset
,长度为len
的数据放置在队列的末尾。此函数通常比driver_enq
更快,因为不需要复制数据。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
返回值为 0
。
driver_enqv()
int driver_enqv(ErlDrvPort port, ErlIOVec *ev,
ErlDrvSizeT skip);
将ev
中的数据排入驱动程序队列的末尾,跳过它的前skip
个字节。它比driver_enq
更快,因为不需要复制数据。
返回值为 0
。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_failure()
driver_failure_atom()
driver_failure_posix()
int driver_failure(ErlDrvPort port, int
error);
int driver_failure_atom(ErlDrvPort port, char
*string);
int driver_failure_posix(ErlDrvPort port, int
error);
向 Erlang 发出信号,表示驱动程序遇到错误并且即将关闭。端口关闭,并且元组 {'EXIT', error, Err}
被发送到端口所有者进程,其中 error 是一个错误原子(driver_failure_atom
和 driver_failure_posix
)或一个整数 (driver_failure
)。
仅当出现严重错误情况(例如,缓冲区分配内存不足时),驱动程序才会发生故障,这时驱动程序无法继续打开。对于正常错误,使用driver_output
发送错误代码更为合适。
返回值为 0
。
driver_failure_eof()
int driver_failure_eof(ErlDrvPort
port);
向 Erlang 发出信号,表示驱动程序遇到 EOF 并且即将关闭,除非端口以选项eof
打开,在这种情况下,eof
被发送到端口。否则,端口关闭,并且 'EXIT'
消息被发送到端口所有者进程。
返回值为 0
。
driver_free()
void driver_free(void *ptr);
释放ptr
指向的内存。内存必须使用driver_alloc
分配。所有已分配的内存都必须只释放一次。驱动程序中没有垃圾回收。
此函数是线程安全的。
driver_free_binary()
void driver_free_binary(ErlDrvBinary *bin);
释放之前使用 driver_alloc_binary
分配的驱动程序二进制文件bin
。由于 Erlang 中的二进制文件是引用计数的,因此该二进制文件仍然可以存在。
此函数是线程安全的。
driver_get_monitored_process()
ErlDrvTermData driver_get_monitored_process(ErlDrvPort port, const
ErlDrvMonitor *monitor);
返回与活动的监视器关联的进程 ID。它可以在process_exit
回调中使用,以获取退出进程的进程标识。
如果监视器不再存在,则返回driver_term_nil
。
driver_get_now()
int driver_get_now(ErlDrvNowData *now);
警告
此函数已弃用。请勿使用。请改用
erl_drv_monotonic_time
(可能与erl_drv_time_offset
结合使用)。
将时间戳读取到参数now
指向的内存中。有关特定字段的信息,请参阅ErlDrvNowData
。
返回值是0
,除非now
指针无效,在这种情况下,返回值是< 0
。
driver_lock_driver()
int driver_lock_driver(ErlDrvPort
port);
将端口port
使用的驱动程序锁定在内存中,以便在模拟器进程的其余生命周期内使用。在此调用之后,驱动程序的行为类似于 Erlang 的静态链接驱动程序之一。
driver_mk_atom()
ErlDrvTermData driver_mk_atom(char*
string);
返回给定名称string
的原子。原子已创建且不会更改,因此可以保存并重用返回值,这比多次查找原子更快。
请注意,此函数是非线程安全的。
driver_mk_port()
ErlDrvTermData driver_mk_port(ErlDrvPort
port);
将端口句柄转换为 Erlang 术语格式,可在 erl_drv_output_term
和 erl_drv_send_term
中使用。
请注意,此函数是非线程安全的。
driver_monitor_process()
int driver_monitor_process(ErlDrvPort port,
ErlDrvTermData process, ErlDrvMonitor *monitor);
开始从驱动程序监视进程。当监视某个进程时,进程退出会导致调用process_exit
回调,该回调在ErlDrvEntry
结构中提供。填写ErlDrvMonitor
结构,以便稍后删除或比较。
参数process
应该是先前调用driver_caller
或driver_connected
调用的返回值。
成功时返回0
,如果没有提供回调则返回 < 0,如果进程不再活动则返回 > 0。
driver_output()
int driver_output(ErlDrvPort port, char *buf,
ErlDrvSizeT len);
将数据从驱动程序发送到模拟器。根据驱动程序端口的打开方式,数据以术语或二进制数据的形式接收。
数据在端口所有者进程的消息队列中排队。请注意,这不会产生对模拟器的让步(因为驱动程序和模拟器在同一个线程中运行)。
参数buf
指向要发送的数据,len
是字节数。
所有输出函数的返回值在正常使用时均为0
。如果驱动程序用于分发,它可能会失败并返回-1
。
driver_output_binary()
int driver_output_binary(ErlDrvPort port, char
*hbuf, ErlDrvSizeT hlen, ErlDrvBinary* bin, ErlDrvSizeT offset,
ErlDrvSizeT len);
从驱动程序二进制文件向端口所有者进程发送数据。它具有标头缓冲区(hbuf
和hlen
),就像driver_output2
一样。参数hbuf
可以为NULL
。
参数offset
是二进制文件中的偏移量,len
是要发送的字节数。
驱动程序二进制文件使用driver_alloc_binary
创建。
标头中的数据作为列表发送,二进制文件作为 Erlang 二进制文件发送到列表的尾部。
例如,如果hlen
是2
,则端口所有者进程接收[H1, H2 | <<T>>]
。
正常使用时的返回值为0
。
请注意,使用 Erlang 中的二进制语法,驱动程序应用程序可以直接从二进制文件中匹配标头,因此可以将标头放入二进制文件中,并将hlen
设置为0
。
driver_output_term()
int driver_output_term(ErlDrvPort port,
ErlDrvTermData* term, int n);
警告
此函数已弃用。请改用
erl_drv_output_term
。
参数term
和n
的工作方式与erl_drv_output_term
中相同。
请注意,此函数是非线程安全的。
driver_output2()
int driver_output2(ErlDrvPort port, char *hbuf,
ErlDrvSizeT hlen, char *buf, ErlDrvSizeT len);
首先发送hbuf
(长度在hlen
中)数据作为列表,而不考虑端口设置。然后将buf
作为二进制文件或列表发送。例如,如果hlen
是3
,则端口所有者进程接收[H1, H2, H3 | T]
。
将数据作为列表标头发送的目的是方便匹配接收到的数据。
正常使用时的返回值为0
。
driver_outputv()
int driver_outputv(ErlDrvPort port, char* hbuf,
ErlDrvSizeT hlen, ErlIOVec *ev, ErlDrvSizeT skip);
将数据从 I/O 向量ev
发送到端口所有者进程。它具有标头缓冲区(hbuf
和hlen
),就像driver_output2
一样。
参数skip
是要从头部跳过的ev
向量的字节数。
您可以从驱动程序队列(见下文)和outputv
驱动程序入口函数中获取ErlIOVec
类型的向量。如果您想一次发送多个ErlDrvBinary
缓冲区,您也可以自己创建它们。通常,使用driver_output
或。更快。
例如,如果hlen
是2
,并且ev
指向一个包含三个二进制文件的数组,则端口所有者进程接收[H1, H2, <<B1>>, <<B2>> | <<B3>>]
。
正常使用时的返回值为0
。
driver_output_binary
的注释也适用于driver_outputv
。
driver_pdl_create()
ErlDrvPDL driver_pdl_create(ErlDrvPort port);
创建与port
关联的端口数据锁。
注意
创建端口数据锁后,在对
port
的驱动程序队列执行所有操作期间,必须对其进行锁定。
成功时返回新创建的端口数据锁,否则返回NULL
。如果port
无效,或者端口数据锁已与port
关联,则该函数失败。
driver_pdl_dec_refc()
long driver_pdl_dec_refc(ErlDrvPDL
pdl);
递减作为参数传递的端口数据锁的引用计数 (pdl
)。
返回执行递减操作后的当前引用计数。
此函数是线程安全的。
driver_pdl_get_refc()
long driver_pdl_get_refc(ErlDrvPDL pdl);
返回作为参数传递的端口数据锁的当前引用计数 (pdl
)。
此函数是线程安全的。
driver_pdl_inc_refc()
long driver_pdl_inc_refc(ErlDrvPDL pdl);
递增作为参数传递的端口数据锁的引用计数 (pdl
)。
返回执行递增操作后的当前引用计数。
此函数是线程安全的。
driver_pdl_lock()
void driver_pdl_lock(ErlDrvPDL pdl);
锁定作为参数传递的端口数据锁 (pdl
)。
此函数是线程安全的。
driver_pdl_unlock()
void driver_pdl_unlock(ErlDrvPDL pdl);
解锁作为参数传递的端口数据锁 (pdl
)。
此函数是线程安全的。
driver_peekq()
SysIOVec * driver_peekq(ErlDrvPort port, int
*vlen);
将驱动程序队列检索为指向 SysIOVec
数组的指针。它还返回 vlen
中的元素数量。这是从队列中获取数据的两种方法之一。
此函数不会从队列中删除任何内容,必须使用 driver_deq
完成删除。
返回的数组适合与 Unix 系统调用 writev
一起使用。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_peekqv()
ErlDrvSizeT driver_peekqv(ErlDrvPort port,
ErlIOVec *ev);
将驱动程序队列检索到提供的 ErlIOVec
ev
中。它还返回队列大小。这是从队列中获取数据的两种方法之一。
如果 ev
为 NULL
,则返回全部为 1 的值,即类型转换为 ErlDrvSizeT
的 -1
。
此函数不会从队列中删除任何内容,必须使用 driver_deq
完成删除。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
自 OTP R15B 起可用
driver_pushq()
int driver_pushq(ErlDrvPort port, char* buf,
ErlDrvSizeT len);
将数据放置在驱动程序队列的头部。buf
中的数据被复制(len
个字节)并放置在队列的开头。
返回值为 0
。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_pushq_bin()
int driver_pushq_bin(ErlDrvPort port,
ErlDrvBinary *bin, ErlDrvSizeT offset, ErlDrvSizeT len);
将二进制 bin
中偏移量为 offset
,长度为 len
的数据放置在驱动程序队列的头部。它通常比 driver_pushq
更快,因为不需要复制数据。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
返回值为 0
。
driver_pushqv()
int driver_pushqv(ErlDrvPort port, ErlIOVec
*ev, ErlDrvSizeT skip);
将 ev
中的数据(跳过其前 skip
个字节)放置在驱动程序队列的头部。它比 driver_pushq
更快,因为不需要复制数据。
返回值为 0
。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_read_timer()
int driver_read_timer(ErlDrvPort port, unsigned
long *time_left);
读取定时器的当前时间,并将结果放置在 time_left
中。这是超时发生前剩余的毫秒数。
返回值为 0
。
driver_realloc()
void * driver_realloc(void *ptr, ErlDrvSizeT size);
调整内存块的大小,可以在原地调整,也可以通过分配新块、复制数据和释放旧块来调整。返回指向重新分配内存的指针。如果失败(内存不足),则返回 NULL
。(这通常是 realloc
的包装器。)
此函数是线程安全的。
driver_realloc_binary()
ErlDrvBinary * driver_realloc_binary(ErlDrvBinary *bin, ErlDrvSizeT size);
调整驱动程序二进制文件的大小,同时保留数据。
成功时返回调整大小的驱动程序二进制文件。失败时(内存不足)返回 NULL
。
此函数是线程安全的。
driver_select()
int driver_select(ErlDrvPort port, ErlDrvEvent
event, int mode, int on);
驱动程序使用此函数向模拟器提供要检查的事件。这使模拟器能够在异步发生某些情况时调用驱动程序。
参数 event
标识特定于操作系统的事件对象。在 Unix 系统上,使用函数 select
/poll
。事件对象必须是套接字或管道(或 select
/poll
可以使用的其他对象)。在 Windows 上,使用 Win32 API 函数 WaitForMultipleObjects
。这会对事件对象施加其他限制;请参阅 Win32 SDK 文档。
参数 on
设置事件时为 1
,清除事件时为 0
。
参数 mode
是 ERL_DRV_READ
、ERL_DRV_WRITE
和 ERL_DRV_USE
的按位 OR 组合。前两个指定是否等待读取事件和/或写入事件。触发的读取事件会调用 ready_input
,触发的写入事件会调用 ready_output
。
注意
某些操作系统 (Windows) 不区分读取事件和写入事件。触发事件的回调仅取决于
mode
的值。
ERL_DRV_USE
指定我们是否正在使用事件对象,或者是否要关闭它。在 driver_select
返回后,清除所有事件然后关闭事件对象是不安全的。另一个线程可能仍在内部使用事件对象。要安全地关闭事件对象,请使用 ERL_DRV_USE
和 on==0
调用 driver_select
,这将清除所有事件,然后调用 stop_select
,或者在可以安全关闭事件对象时安排调用它。ERL_DRV_USE
应与事件对象的第一个事件一起设置。即使已设置 ERL_DRV_USE
,设置它也是无害的。清除所有事件但保持 ERL_DRV_USE
设置表明我们正在使用事件对象,并且可能会再次为其设置事件。
注意
ERL_DRV_USE
在 Erlang/OTP R13 中添加。旧驱动程序仍然像以前一样工作,但建议更新它们以使用ERL_DRV_USE
和stop_select
,以确保以安全的方式关闭事件对象。
返回值是 0
,除非 ready_input
/ready_output
为 NULL
,在这种情况下,返回值是 -1
。
driver_send_term()
int driver_send_term(ErlDrvPort port,
ErlDrvTermData receiver, ErlDrvTermData* term, int n);
警告
此函数已弃用。 请改用
erl_drv_send_term
。
注意
当由任意线程执行时,运行时系统无法正确检查此函数的参数。这可能会导致该函数在应该失败时不失败。
参数term
和n
的工作方式与erl_drv_output_term
中相同。
此函数是线程安全的。
driver_set_timer()
int driver_set_timer(ErlDrvPort port, unsigned
long time);
在驱动程序上设置一个定时器,该定时器将倒计时并在超时时调用驱动程序。参数 time
是定时器到期前的毫秒数。
当定时器达到 0
并到期时,会调用驱动程序入口函数 timeout
。
请注意,每个驱动程序实例上只有一个定时器;设置新的定时器会替换旧的定时器。
返回值是 0
,除非 timeout
驱动程序函数为 NULL
,在这种情况下,返回值是 -1
。
driver_sizeq()
ErlDrvSizeT driver_sizeq(ErlDrvPort port);
返回驱动程序队列中当前存在的字节数。
如果调用线程在调用期间锁定了与port
关联的端口数据锁,则可以从任何线程调用此函数。
driver_system_info()
void driver_system_info(ErlDrvSysInfo
*sys_info_ptr, size_t size);
将有关 Erlang 运行时系统的信息写入第一个参数引用的 ErlDrvSysInfo
结构中。第二个参数是 ErlDrvSysInfo
结构的大小,即 sizeof(ErlDrvSysInfo)
。
有关特定字段的信息,请参阅 ErlDrvSysInfo
。
driver_vec_to_buf()
ErlDrvSizeT driver_vec_to_buf(ErlIOVec *ev,
char *buf, ErlDrvSizeT len);
通过按顺序将 ev
引用的多个数据段复制到大小为 len
的缓冲区 buf
中,来收集这些数据段。
如果要将数据从驱动程序发送到端口所有者进程,则使用 driver_outputv
会更快。
返回值是缓冲区中剩余的空间,即如果 ev
包含的字节数少于 len
个字节,则返回差值,如果 ev
包含 len
个或更多字节,则返回 0
。如果有多个标头字节,这将更快,因为二进制语法可以直接从二进制构造整数。
erl_drv_busy_msgq_limits()
void erl_drv_busy_msgq_limits(ErlDrvPort port,
ErlDrvSizeT *low, ErlDrvSizeT *high);
设置和获取用于控制端口消息队列忙碌状态的限制。
当消息队列上排队的命令数据量达到 high
限制时,端口消息队列将设置为忙碌状态。当消息队列上排队的命令数据量低于 low
限制时,端口消息队列将设置为非忙碌状态。在这种情况下,命令数据是指使用 Port ! {Owner, {command, Data}}
或 port_command/[2,3]
传递给端口的数据。请注意,这些限制仅涉及尚未到达端口的命令数据。忙碌端口 功能可用于已到达端口的数据。
有效的限制是 [ERL_DRV_BUSY_MSGQ_LIM_MIN, ERL_DRV_BUSY_MSGQ_LIM_MAX]
范围内的值。限制会自动调整为合理的值。也就是说,系统会调整值,使所使用的低限制小于或等于所使用的高限制。默认情况下,高限制为 8 kB,低限制为 4 kB。
通过传递一个指向包含值 ERL_DRV_BUSY_MSGQ_READ_ONLY
的整数变量的指针,读取当前使用的限制并将其写回整数变量。可以通过传递一个指向包含有效限制的整数变量的指针来设置新的限制。传递的值将写入内部限制。然后调整内部限制。之后,将调整后的限制写回从中读取新值的整数变量。值以字节为单位。
繁忙消息队列功能可以通过以下两种方式禁用:在驱动程序使用的 driver_entry
中设置 ERL_DRV_FLAG_NO_BUSY_MSGQ
驱动标志,或者调用此函数并将 ERL_DRV_BUSY_MSGQ_DISABLED
作为限制(低限制或高限制)。禁用此功能后,将无法再次启用。读取限制时,如果此功能已禁用,则两者均为 ERL_DRV_BUSY_MSGQ_DISABLED
。
如果端口繁忙或端口消息队列繁忙,则向端口发送命令数据的进程将被挂起。当端口和端口消息队列都不繁忙时,挂起的进程将恢复。
有关繁忙端口功能的信息,请参阅 set_busy_port
。
自 OTP R16B 起可用
erl_drv_cond_broadcast()
void erl_drv_cond_broadcast(ErlDrvCond
*cnd);
在条件变量上广播。也就是说,如果有其他线程在等待正在广播的条件变量,那么所有这些线程都会被唤醒。
cnd
是指向要广播的条件变量的指针。
此函数是线程安全的。
erl_drv_cond_create()
ErlDrvCond * erl_drv_cond_create(char
*name);
创建一个条件变量并返回指向它的指针。
name
是一个字符串,用于标识创建的条件变量。它用于在计划的未来调试功能中标识条件变量。
失败时返回 NULL
。创建条件变量的驱动程序负责在卸载驱动程序之前销毁它。
此函数是线程安全的。
erl_drv_cond_destroy()
void erl_drv_cond_destroy(ErlDrvCond
*cnd);
销毁先前由 erl_drv_cond_create
创建的条件变量。
cnd
是指向要销毁的条件变量的指针。
此函数是线程安全的。
erl_drv_cond_name()
char * erl_drv_cond_name(ErlDrvCond
*cnd);
返回指向条件名称的指针。
cnd
是指向已初始化条件的指针。
注意
此函数仅用于调试目的。
自 OTP R16B02 起可用
erl_drv_cond_signal()
void erl_drv_cond_signal(ErlDrvCond
*cnd);
在条件变量上发出信号。也就是说,如果有其他线程在等待正在发出信号的条件变量,那么它们中的一个会被唤醒。
cnd
是指向要发出信号的条件变量的指针。
此函数是线程安全的。
erl_drv_cond_wait()
void erl_drv_cond_wait(ErlDrvCond *cnd,
ErlDrvMutex *mtx);
等待条件变量。调用线程将被阻塞,直到另一个线程通过在条件变量上发出信号或广播来唤醒它。在调用线程被阻塞之前,它会解锁作为参数传递的互斥锁。当调用线程被唤醒时,它会在返回之前锁定相同的互斥锁。也就是说,调用此函数时,互斥锁当前必须由调用线程锁定。
cnd
是指向要等待的条件变量的指针。mtx
是指向等待时要解锁的互斥锁的指针。
注意
即使没有人发出信号或在条件变量上广播,
erl_drv_cond_wait
也可以返回。调用erl_drv_cond_wait
的代码始终要准备好erl_drv_cond_wait
返回,即使线程正在等待的条件没有发生。也就是说,从erl_drv_cond_wait
返回时,始终检查条件是否发生,如果没有,则再次调用erl_drv_cond_wait
。
此函数是线程安全的。
erl_drv_consume_timeslice()
int erl_drv_consume_timeslice(ErlDrvPort port,
int percent);
向运行时系统提示当前驱动程序回调调用自上次提示以来或自回调开始以来消耗了多少 CPU 时间(如果没有给出先前的提示)。
port
- 执行端口的端口句柄。percent
- 以百分比表示的完整时间片的大致消耗比例。
时间被指定为一个完整时间片的百分比,一个端口在将 CPU 让给其他可运行端口或进程之前允许执行的时间片。有效范围是 [1, 100]
。调度时间片不是一个精确的实体,但通常可以近似为大约 1 毫秒。
请注意,由运行时系统决定是否以及如何使用此信息。某些平台上的实现可以使用其他方法来确定时间片的消耗比例。无论如何,冗长的驱动程序回调应频繁调用此函数,以确定是否允许继续执行。
如果时间片已耗尽,此函数将返回非零值;如果允许回调继续执行,则返回零。如果返回非零值,则驱动程序回调应尽快返回,以便端口能够让出。
提供此函数是为了更好地支持协作调度、提高系统响应能力,并使其更容易防止由于端口独占调度程序线程而导致的 VM 行为不当。当将冗长的工作划分为一些重复的驱动程序回调调用时,可以使用它,而无需使用线程。
另请参阅本手册页开头重要的 警告文本。
自 OTP R16B 起可用
erl_drv_convert_time_unit()
ErlDrvTime erl_drv_convert_time_unit(ErlDrvTime
val, ErlDrvTimeUnit from, ErlDrvTimeUnit to);
将时间单位 from
的 val
值转换为时间单位 to
的对应值。结果使用 floor 函数进行舍入。
val
- 要转换时间单位的值。from
-val
的时间单位。to
- 返回值的时间单位。
如果使用无效的时间单位参数调用,则返回 ERL_DRV_TIME_ERROR
。
另请参阅 ErlDrvTime
和 ErlDrvTimeUnit
。
自 OTP 18.3 起可用
erl_drv_equal_tids()
int erl_drv_equal_tids(ErlDrvTid tid1,
ErlDrvTid tid2);
比较两个线程标识符 tid1
和 tid2
是否相等。
如果不相等,则返回 0
;如果相等,则返回一个不等于 0
的值。
注意
线程终止后,线程标识符可以非常快地被重用。因此,如果自保存线程标识符以来,与其中一个涉及的线程标识符对应的线程已终止,则
erl_drv_equal_tids
的结果可能不会给出预期的结果。
此函数是线程安全的。
erl_drv_getenv()
int erl_drv_getenv(const char *key, char
*value, size_t *value_size);
检索环境变量的值。
key
- 一个以NULL
结尾的字符串,其中包含环境变量的名称。value
- 指向输出缓冲区的指针。value_size
- 指向整数的指针。该整数用于传递输入和输出大小(见下文)。
调用此函数时,*value_size
应包含 value
缓冲区的大小。
成功时,返回 0
,环境变量的值已写入 value
缓冲区,并且 *value_size
包含写入 value
缓冲区的字符串的长度(不包括终止 NULL
字符)。
失败时,即未找到此类环境变量时,返回一个 0
的值。当 value
缓冲区的大小太小时,返回一个 0
的值,并且 *value_size
已设置为所需的缓冲区大小。
警告
此函数读取
os:getenv/1
使用的模拟环境,而不是 libc 的getenv(3)
或类似函数使用的环境。需要 这些同步的驱动程序需要自行同步,但请记住,它们是出于某种原因而隔离的;getenv(3)
及其朋友不是线程安全的,并且可能导致不相关的代码行为不当或使模拟器崩溃。
此函数是线程安全的。
erl_drv_init_ack()
void erl_drv_init_ack(ErlDrvPort port,
ErlDrvData res);
确认端口的启动。
port
- 执行确认的端口(驱动程序实例)的端口句柄。res
- 端口初始化的结果。可以是与start
的返回值相同的值,即任何错误代码或用于此端口的ErlDrvData
。
调用此函数时,启动的 erlang:open_port
调用将返回,就像刚刚调用了 start
函数一样。它仅当在链接的驱动程序上设置了标志 ERL_DRV_FLAG_USE_INIT_ACK
时才可以使用。
自 OTP 19.0 起可用
erl_drv_monotonic_time()
ErlDrvTime erl_drv_monotonic_time(ErlDrvTimeUnit time_unit);
返回 Erlang 单调时间。请注意,负值并不少见。
time_unit
是返回的时间单位的值。
如果使用无效的时间单位参数调用,或者从不是调度程序线程的线程调用,则返回 ERL_DRV_TIME_ERROR
。
另请参阅 ErlDrvTime
和 ErlDrvTimeUnit
。
自 OTP 18.3 起可用
erl_drv_mutex_create()
ErlDrvMutex * erl_drv_mutex_create(char
*name);
创建一个互斥锁并返回指向它的指针。
name
是一个字符串,用于标识创建的互斥锁。它用于在调试功能中标识互斥锁(请参阅注释)。
失败时返回 NULL
。创建互斥锁的驱动程序负责在卸载驱动程序之前销毁它。
此函数是线程安全的。
注意
其中一种调试功能是锁检查器,它可以检测锁定顺序违规,从而检测潜在的死锁错误。为了使锁检查器工作,
name
的格式应为"App.Type"
或"App.Type[Instance]"
,其中 App 是应用程序的名称,Type 是锁类型的名称,Instance 是关于每个锁实例的可选信息。“App.Type”应是唯一的名称,以便锁检查器检测不同类型锁之间的锁定顺序违规。当前忽略 Instance 信息。例如,如果我们有 “myapp.xtable” 和 “myapp.xitem” 类型的互斥锁,则锁检查器将确保 “myapp.xtable” 锁永远不会在 “myapp.xitem” 锁之后锁定,反之亦然。
erl_drv_mutex_destroy()
void erl_drv_mutex_destroy(ErlDrvMutex
*mtx);
销毁先前由 erl_drv_mutex_create
创建的互斥锁。互斥锁在销毁之前必须处于解锁状态。
mtx
是指向要销毁的互斥锁的指针。
此函数是线程安全的。
erl_drv_mutex_lock()
void erl_drv_mutex_lock(ErlDrvMutex
*mtx);
锁定互斥锁。调用线程将被阻塞,直到互斥锁被锁定。当前已锁定互斥锁的线程不能再次锁定同一互斥锁。
mtx
是指向要锁定的互斥锁的指针。
警告
如果在让线程失去控制时,你在模拟器线程中保持互斥锁锁定,则很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_mutex_name()
char * erl_drv_mutex_name(ErlDrvMutex
*mtx);
返回指向互斥锁名称的指针。
mtx
是指向已初始化互斥锁的指针。
注意
此函数仅用于调试目的。
自 OTP R16B02 起可用
erl_drv_mutex_trylock()
int erl_drv_mutex_trylock(ErlDrvMutex
*mtx);
尝试锁定互斥锁。当前已锁定互斥锁的线程不能再次尝试锁定同一互斥锁。
mtx
是指向要尝试锁定的互斥锁的指针。
成功时返回 0
,否则返回 EBUSY
。
警告
如果在让线程失去控制时,你在模拟器线程中保持互斥锁锁定,则很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_mutex_unlock()
void erl_drv_mutex_unlock(ErlDrvMutex
*mtx);
解锁互斥锁。互斥锁当前必须由调用线程锁定。
mtx
是指向要解锁的互斥锁的指针。
此函数是线程安全的。
erl_drv_output_term()
int erl_drv_output_term(ErlDrvTermData port,
ErlDrvTermData* term, int n);
以特殊的驱动程序术语格式向端口所有者进程发送数据。这是一种从驱动程序传递术语数据的快速方法。它不需要二进制转换,因此端口所有者进程接收数据为正常的 Erlang 术语。erl_drv_send_term
函数可用于向本地节点上的任何进程发送数据。
注意
参数
port
不是普通的端口句柄,而是使用driver_mk_port
转换后的端口句柄。
参数 term
指向一个包含 n
个元素的 ErlDrvTermData
数组。此数组包含以驱动程序术语格式描述的术语。每个术语包含数组中的 1-4 个元素。第一个术语具有术语类型,然后是参数。参数 port
指定发送端口。
元组、映射和列表(字符串除外,请参见下文)以逆波兰表示法构建,因此要构建元组,首先指定元素,然后指定元组术语,并带有计数。列表和映射也是如此。
- 必须指定元组的元素数量。(元素在
ERL_DRV_TUPLE
术语之前。) - 必须指定映射的键值对数量
N
。键值对必须按此顺序位于ERL_DRV_MAP
之前:key1,value1,key2,value2,...,keyN,valueN
。不允许重复的键。 - 必须指定列表的元素数量,包括尾部,尾部是
ERL_DRV_LIST
之前的最后一个术语。
特殊术语 ERL_DRV_STRING_CONS
用于在列表中“拼接”字符串,以这种方式指定的字符串本身不是列表,但元素是周围列表的元素。
Term type Arguments
--------- ---------
ERL_DRV_NIL
ERL_DRV_ATOM ErlDrvTermData atom (from driver_mk_atom(char *string))
ERL_DRV_INT ErlDrvSInt integer
ERL_DRV_UINT ErlDrvUInt integer
ERL_DRV_INT64 ErlDrvSInt64 *integer_ptr
ERL_DRV_UINT64 ErlDrvUInt64 *integer_ptr
ERL_DRV_PORT ErlDrvTermData port (from driver_mk_port(ErlDrvPort port))
ERL_DRV_BINARY ErlDrvBinary *bin, ErlDrvUInt len, ErlDrvUInt offset
ERL_DRV_BUF2BINARY char *buf, ErlDrvUInt len
ERL_DRV_STRING char *str, int len
ERL_DRV_TUPLE int sz
ERL_DRV_LIST int sz
ERL_DRV_PID ErlDrvTermData pid (from driver_connected(ErlDrvPort port)
or driver_caller(ErlDrvPort port))
ERL_DRV_STRING_CONS char *str, int len
ERL_DRV_FLOAT double *dbl
ERL_DRV_EXT2TERM char *buf, ErlDrvUInt len
ERL_DRV_MAP int sz
无符号整数数据类型 ErlDrvUInt
和有符号整数数据类型 ErlDrvSInt
在 64 位运行时系统上为 64 位宽,在 32 位运行时系统上为 32 位宽。它们是在 ERTS 5.6 中引入的,并取代了上面列表中的一些 int
参数。
无符号整数数据类型 ErlDrvUInt64
和有符号整数数据类型 ErlDrvSInt64
始终为 64 位宽。它们是在 ERTS 5.7.4 中引入的。
要构建元组 {tcp, Port, [100 | Binary]}
,可以进行以下调用。
ErlDrvBinary* bin = ...
ErlDrvPort port = ...
ErlDrvTermData spec[] = {
ERL_DRV_ATOM, driver_mk_atom("tcp"),
ERL_DRV_PORT, driver_mk_port(drvport),
ERL_DRV_INT, 100,
ERL_DRV_BINARY, bin, 50, 0,
ERL_DRV_LIST, 2,
ERL_DRV_TUPLE, 3,
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
此处 bin
是长度至少为 50 的驱动程序二进制文件,而 drvport
是端口句柄。请注意,ERL_DRV_LIST
位于列表的元素之后,同样,ERL_DRV_TUPLE
也是如此。
ERL_DRV_STRING_CONS
术语是一种构造字符串的方法。它的工作方式与 ERL_DRV_STRING
的工作方式不同。ERL_DRV_STRING_CONS
以相反的顺序构建字符串列表(与 ERL_DRV_LIST
的工作方式相反),将添加到列表的字符串连接起来。尾部必须在 ERL_DRV_STRING_CONS
之前指定。
ERL_DRV_STRING
构造一个字符串并结束它。(因此,它与 ERL_DRV_NIL
后跟 ERL_DRV_STRING_CONS
相同。)
/* to send [x, "abc", y] to the port: */
ErlDrvTermData spec[] = {
ERL_DRV_ATOM, driver_mk_atom("x"),
ERL_DRV_STRING, (ErlDrvTermData)"abc", 3,
ERL_DRV_ATOM, driver_mk_atom("y"),
ERL_DRV_NIL,
ERL_DRV_LIST, 4
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
/* to send "abc123" to the port: */
ErlDrvTermData spec[] = {
ERL_DRV_NIL, /* with STRING_CONS, the tail comes first */
ERL_DRV_STRING_CONS, (ErlDrvTermData)"123", 3,
ERL_DRV_STRING_CONS, (ErlDrvTermData)"abc", 3,
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
ERL_DRV_EXT2TERM
术语类型用于传递使用 外部格式 编码的术语,也就是说,该术语已由 erlang:term_to_binary()
、erl_interface:ei(3)
等编码。例如,如果 binp
是指向 ErlDrvBinary
的指针,该 ErlDrvBinary
包含使用 外部格式 编码的术语 {17, 4711}
,并且你想将其包装在带有标记 my_tag
的二元组中,即 {my_tag, {17, 4711}}
,你可以按如下方式进行
ErlDrvTermData spec[] = {
ERL_DRV_ATOM, driver_mk_atom("my_tag"),
ERL_DRV_EXT2TERM, (ErlDrvTermData) binp->orig_bytes, binp->orig_size
ERL_DRV_TUPLE, 2,
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
要构建映射 #{key1 => 100, key2 => {200, 300}}
,可以进行以下调用。
ErlDrvPort port = ...
ErlDrvTermData spec[] = {
ERL_DRV_ATOM, driver_mk_atom("key1"),
ERL_DRV_INT, 100,
ERL_DRV_ATOM, driver_mk_atom("key2"),
ERL_DRV_INT, 200,
ERL_DRV_INT, 300,
ERL_DRV_TUPLE, 2,
ERL_DRV_MAP, 2
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
如果你想传递一个二进制文件,并且 ErlDrvBinary
中还没有该二进制文件的内容,那么你可以受益于使用 ERL_DRV_BUF2BINARY
,而不是通过 driver_alloc_binary
创建 ErlDrvBinary
,然后通过 ERL_DRV_BINARY
传递二进制文件。如果使用 ERL_DRV_BUF2BINARY
,运行时系统通常会更智能地分配二进制文件。但是,如果要传递的二进制文件的内容已经驻留在 ErlDrvBinary
中,那么通常最好使用 ERL_DRV_BINARY
和相关的 ErlDrvBinary
传递二进制文件。
ERL_DRV_UINT
、ERL_DRV_BUF2BINARY
和 ERL_DRV_EXT2TERM
术语类型是在 ERTS 5.6 中引入的。
此函数是线程安全的。
自 OTP R16B 起可用
erl_drv_putenv()
int erl_drv_putenv(const char *key, char
*value);
设置环境变量的值。
key
是一个以 NULL
结尾的字符串,其中包含环境变量的名称。
value
是一个以 NULL
结尾的字符串,其中包含环境变量的新值。
成功时返回 0
,否则返回 != 0
的值。
注意
将空字符串 (
""
) 作为值传递的结果取决于平台。在某些平台上,变量值设置为空字符串,而在另一些平台上,则删除环境变量。
警告
此函数会修改
os:putenv/2
使用的模拟环境,而不是 libc 的putenv(3)
或类似环境使用的环境。需要这些环境同步的驱动程序将需要自己完成同步,但请记住,它们被隔离是有原因的;putenv(3)
及其朋友不是线程安全的,可能会导致不相关的代码行为异常或使模拟器崩溃。
此函数是线程安全的。
erl_drv_rwlock_create()
ErlDrvRWLock * erl_drv_rwlock_create(char
*name);
创建一个读写锁并返回指向它的指针。
name
是一个字符串,用于标识创建的读写锁。它用于在调试功能中标识读写锁(请参阅有关锁检查器的说明)。
失败时返回 NULL
。创建读写锁的驱动程序负责在卸载驱动程序之前销毁它。
此函数是线程安全的。
erl_drv_rwlock_destroy()
void erl_drv_rwlock_destroy(ErlDrvRWLock
*rwlck);
销毁先前由 erl_drv_rwlock_create
创建的读写锁。在销毁之前,读写锁必须处于解锁状态。
rwlck
是指向要销毁的读写锁的指针。
此函数是线程安全的。
erl_drv_rwlock_name()
char * erl_drv_rwlock_name(ErlDrvRWLock
*rwlck);
返回指向读写锁名称的指针。
rwlck
是指向已初始化读写锁的指针。
注意
此函数仅用于调试目的。
自 OTP R16B02 起可用
erl_drv_rwlock_rlock()
void erl_drv_rwlock_rlock(ErlDrvRWLock
*rwlck);
读取锁定读写锁。调用线程将被阻塞,直到读写锁被读取锁定。当前已读取锁定或读写锁定读写锁的线程不能再次锁定同一读写锁。
rwlck
是指向要读取锁定的读写锁的指针。
警告
如果在你让线程失去控制时,你将读写锁锁定在模拟器线程中,你很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_rwlock_runlock()
void erl_drv_rwlock_runlock(ErlDrvRWLock
*rwlck);
读取解锁读写锁。读写锁当前必须由调用线程读取锁定。
rwlck
是指向要读取解锁的读写锁的指针。
此函数是线程安全的。
erl_drv_rwlock_rwlock()
void erl_drv_rwlock_rwlock(ErlDrvRWLock
*rwlck);
读写锁定读写锁。调用线程将被阻塞,直到读写锁被读写锁定。当前已读取锁定或读写锁定读写锁的线程不能再次锁定同一读写锁。
rwlck
是指向要读写锁定的读写锁的指针。
警告
如果在你让线程失去控制时,你将读写锁锁定在模拟器线程中,你很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_rwlock_rwunlock()
void erl_drv_rwlock_rwunlock(ErlDrvRWLock
*rwlck);
读写解锁读写锁。读写锁当前必须由调用线程读写锁定。
rwlck
是指向要读写解锁的读写锁的指针。
此函数是线程安全的。
erl_drv_rwlock_tryrlock()
int erl_drv_rwlock_tryrlock(ErlDrvRWLock
*rwlck);
尝试读取锁定读写锁。
rwlck
是指向要尝试读取锁定的读写锁的指针。
成功时返回 0
,否则返回 EBUSY
。当前已读取锁定或读写锁定读写锁的线程不能再次尝试锁定同一读写锁。
警告
如果在你让线程失去控制时,你将读写锁锁定在模拟器线程中,你很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_rwlock_tryrwlock()
int erl_drv_rwlock_tryrwlock(ErlDrvRWLock
*rwlck);
尝试读写锁定读写锁。当前已读取锁定或读写锁定读写锁的线程不能再次尝试锁定同一读写锁。
rwlck
是指向要尝试读写锁定的读写锁的指针。
成功时返回 0
,否则返回 EBUSY
。
警告
如果在你让线程失去控制时,你将读写锁锁定在模拟器线程中,你很可能会使整个模拟器死锁。
此函数是线程安全的。
erl_drv_send_term()
int erl_drv_send_term(ErlDrvTermData port,
ErlDrvTermData receiver, ErlDrvTermData* term, int n);
此函数是驱动程序向除端口所有者进程之外的其他进程发送数据的唯一方法。参数 receiver
指定接收数据的进程。
注意
参数
port
不是普通的端口句柄,而是使用driver_mk_port
转换后的端口句柄。
参数 port
、term
和 n
的工作方式与 erl_drv_output_term
中相同。
此函数是线程安全的。
自 OTP R16B 起可用
erl_drv_set_os_pid()
void erl_drv_set_os_pid(ErlDrvPort port,
ErlDrvSInt pid);
设置在此端口上执行 erlang:port_info/2
时看到的 os_pid
。
port
是要设置 pid 的端口(驱动程序实例)的端口句柄。pid
是要设置的 pid。
自 OTP 19.0 起可用
erl_drv_thread_create()
int erl_drv_thread_create(char *name, ErlDrvTid
*tid, void * (*func)(void *), void *arg, ErlDrvThreadOpts
*opts);
创建一个新线程。
name
- 一个字符串,用于标识创建的线程。它用于在计划的未来调试功能中标识线程。tid
- 指向线程标识符变量的指针。func
- 指向要在创建的线程中执行的函数的指针。arg
- 指向func
函数的参数的指针。opts
- 指向要使用的线程选项的指针,或NULL
。
成功时返回 0
,否则返回一个 errno
值来指示错误。新创建的线程开始在 func
指向的函数中执行,并且 func
传递 arg
作为参数。当 erl_drv_thread_create
返回时,新创建的线程的线程标识符在 *tid
中可用。opts
可以是 NULL
指针,也可以是指向 ErlDrvThreadOpts
结构的指针。如果 opts
是 NULL
指针,则使用默认选项,否则使用传递的选项。
警告
不允许自行分配
ErlDrvThreadOpts
结构。它必须由erl_drv_thread_opts_create
分配和初始化。
创建的线程在 func
返回时或如果线程调用了 erl_drv_thread_exit
时终止。线程的退出值要么从 func
返回,要么作为参数传递给 erl_drv_thread_exit
。创建线程的驱动程序负责在卸载驱动程序之前,通过 erl_drv_thread_join
加入线程。不能创建“分离”线程,也就是说,不需要加入的线程。
警告
所有创建的线程必须在驱动程序卸载之前由该驱动程序加入。如果驱动程序未能加入在卸载之前创建的所有线程,则当驱动程序代码被卸载时,运行时系统很可能会崩溃。
此函数是线程安全的。
erl_drv_thread_exit()
void erl_drv_thread_exit(void
*exit_value);
使用作为参数传递的退出值终止调用线程。exit_value
是指向退出值的指针或 NULL
。
只允许终止使用 erl_drv_thread_create
创建的线程。
退出值稍后可以通过另一个线程通过 erl_drv_thread_join
检索。
此函数是线程安全的。
erl_drv_thread_join()
int erl_drv_thread_join(ErlDrvTid tid, void
**exit_value);
将调用线程与另一个线程连接,也就是说,调用线程被阻塞,直到 tid
标识的线程终止。
tid
是要加入的线程的线程标识符。exit_value
是指向退出值的指针的指针,或 NULL
。
成功时返回 0
,否则返回一个 errno
值来指示错误。
一个线程只能被加入一次。多次加入的行为是未定义的,可能会导致模拟器崩溃。如果 exit_value == NULL
,则忽略终止线程的退出值,否则终止线程的退出值将存储在 *exit_value
中。
此函数是线程安全的。
erl_drv_thread_name()
char * erl_drv_thread_name(ErlDrvTid
tid);
返回指向线程名称的指针。
tid
是一个线程标识符。
注意
此函数仅用于调试目的。
自 OTP R16B02 起可用
erl_drv_thread_opts_create()
ErlDrvThreadOpts * erl_drv_thread_opts_create(char *name);
分配并初始化一个线程选项结构。
name
是一个字符串,用于标识创建的线程选项。它用于在计划的未来调试功能中标识线程选项。
失败时返回 NULL
。线程选项结构用于将选项传递给 erl_drv_thread_create
。如果该结构在传递给 erl_drv_thread_create
之前未被修改,则使用默认值。
警告
不允许自行分配
ErlDrvThreadOpts
结构。它必须由erl_drv_thread_opts_create
分配和初始化。
此函数是线程安全的。
erl_drv_thread_opts_destroy()
void erl_drv_thread_opts_destroy(ErlDrvThreadOpts *opts);
销毁先前由 erl_drv_thread_opts_create
创建的线程选项。
opts
是指向要销毁的线程选项的指针。
此函数是线程安全的。
erl_drv_thread_self()
ErlDrvTid erl_drv_thread_self(void);
返回调用线程的线程标识符。
此函数是线程安全的。
erl_drv_time_offset()
ErlDrvTime erl_drv_time_offset(ErlDrvTimeUnit
time_unit);
返回 Erlang 单调时间 和 Erlang 系统时间 之间转换为作为参数传递的 time_unit
的当前时间偏移量。
time_unit
是返回的时间单位的值。
如果使用无效的时间单位参数调用,或者从不是调度程序线程的线程调用,则返回 ERL_DRV_TIME_ERROR
。
另请参阅 ErlDrvTime
和 ErlDrvTimeUnit
。
自 OTP 18.3 起可用
erl_drv_tsd_get()
void * erl_drv_tsd_get(ErlDrvTSDKey
key);
返回与调用线程的 key
关联的线程特定数据。
key
是一个线程特定的数据键。
如果调用线程没有与 key
关联的数据,则返回 NULL
。
此函数是线程安全的。
erl_drv_tsd_key_create()
int erl_drv_tsd_key_create(char *name,
ErlDrvTSDKey *key);
创建一个线程特定的数据键。
name
是一个字符串,用于标识创建的键。它用于在计划的未来调试功能中标识该键。
key
是指向线程特定数据键变量的指针。
成功时返回 0
,否则返回一个 errno
值来指示错误。创建该键的驱动程序负责在卸载驱动程序之前销毁它。
此函数是线程安全的。
erl_drv_tsd_key_destroy()
void erl_drv_tsd_key_destroy(ErlDrvTSDKey
key);
销毁先前由 erl_drv_tsd_key_create
创建的线程特定的数据键。在使用此键的所有线程中的所有线程特定数据都必须清除(参见 erl_drv_tsd_set
),然后才能调用 erl_drv_tsd_key_destroy
。
key
是要销毁的线程特定的数据键。
警告
销毁的键很可能会很快被重用。因此,如果您在销毁键之前未能清除线程中使用此键的线程特定数据,那么您很可能会在系统的其他部分遇到意外错误。
此函数是线程安全的。
erl_drv_tsd_set()
void erl_drv_tsd_set(ErlDrvTSDKey key, void
*data);
为调用线程设置与 key
关联的线程特定数据。您只允许在线程完全受您控制时为其设置线程特定数据。例如,如果您在调用驱动程序回调函数的线程中设置线程特定数据,则必须在从驱动程序回调函数返回之前清除该数据,即将其设置为 NULL
。
key
是一个线程特定的数据键。
data
是指向要与调用线程中的 key
关联的数据的指针。
警告
如果您在将模拟器线程置于您的控制之外之前未能清除其线程特定数据,则您可能永远无法清除此数据,从而导致系统其他部分出现意外错误。
此函数是线程安全的。
erl_errno_id()
char * erl_errno_id(int error);
返回 Erlang 错误的原子名称,给定 error
中的错误编号。错误原子是 einval
、enoent
等。它可以用于从驱动程序创建错误项。
remove_driver_entry()
int remove_driver_entry(ErlDrvEntry
*de);
删除先前使用 add_driver_entry
添加的驱动程序条目 de
。
使用 erl_ddll
Erlang 接口添加的驱动程序条目不能使用此接口删除。
set_busy_port()
void set_busy_port(ErlDrvPort port, int
on);
设置和取消端口的忙碌状态。如果 on
为非零,则端口设置为忙碌。如果为零,则端口设置为不忙碌。您通常希望将此功能与忙碌端口消息队列功能结合使用。
如果端口或端口消息队列忙碌,则向端口发送命令数据的进程将被挂起。当端口和端口消息队列都不忙碌时,挂起的进程将恢复。在此上下文中,命令数据是使用 Port ! {Owner, {command, Data}}
或 port_command/[2,3]
传递到端口的数据。
如果在 ERL_DRV_FLAG_SOFT_BUSY 中设置了 driver_entry
,即使驱动程序已发出忙碌信号,也可以通过 erlang:port_command(Port, Data, [force])
将数据强制放入驱动程序。
有关忙碌端口消息队列功能的信息,请参见 erl_drv_busy_msgq_limits
。
set_port_control_flags()
void set_port_control_flags(ErlDrvPort port,
int flags);
设置 control
驱动程序条目函数如何将数据返回给端口所有者进程的标志。(control
函数是从 erlang:port_control/3
调用的。)
目前,flags
只有两个有意义的值:0
表示数据以列表形式返回,PORT_CONTROL_FLAG_BINARY
表示数据以 control
中的二进制形式返回。
另请参阅
driver_entry(3)
, erlang
, erl_ddll
, 用户指南中如何为 Erlang 分布实现替代载体部分