查看源码 erl_ddll (内核 v10.2)
动态驱动程序加载器和链接器。
此模块提供了一个接口,用于在运行时加载和卸载Erlang 链接驱动程序。
注意
这是一个大型参考文档。对于此模块的偶尔使用,以及对于大多数实际应用,函数
load/2
和unload/1
的描述足以入门。
驱动程序应以特定于所用平台的对象代码格式的动态链接库的形式提供,即大多数 Unix 系统上的 .so
文件和 Windows 上的 .ddl
文件。Erlang 链接驱动程序必须为模拟器提供特定的接口,因此此模块不适用于加载任意动态库。有关 Erlang 驱动程序的更多信息,请参见 erl_driver
。
在描述一组函数(即一个模块、一个模块的一部分或一个应用程序)在进程中执行并想要使用 ddll 驱动程序时,我们使用术语用户。一个进程可以有多个用户(需要相同驱动程序的不同模块),并且多个进程运行相同的代码,构成驱动程序的多个用户。
在基本场景中,每个用户在开始使用驱动程序之前加载它,并在完成后卸载驱动程序。引用计数会跟踪进程和每个进程的加载次数。这样,只有当没有人需要它(它没有用户)时才会卸载驱动程序。驱动程序还会跟踪为其打开的端口。这使得可以延迟卸载,直到所有端口都关闭,或者在卸载时杀死所有使用该驱动程序的端口。
该接口支持两种基本的加载和卸载场景。每种场景还可以选择在卸载驱动程序时杀死端口,还是等待端口自行关闭。这些场景如下:
“按需”加载和卸载 - 此(最常见)场景仅支持驱动程序的每个 用户 在需要时加载它,并在不再需要时卸载它。驱动程序始终是引用计数的,只要保持驱动程序加载的进程仍然存在,驱动程序就存在于系统中。
驱动程序的每个 用户 在要求加载时都使用驱动程序的完全相同的路径名,但是 用户 不关心驱动程序是否已从文件系统加载或是否必须从文件系统加载目标代码。
以下两对函数支持此场景:
load/2 和 unload/1 - 当使用
load/unload
接口时,在最后一个端口使用驱动程序关闭之前,驱动程序不会被卸载。函数unload/1
可以立即返回,因为 用户 不关心卸载何时发生。当不再有人需要它时,驱动程序就会被卸载。如果加载了驱动程序的进程死亡,则其效果与完成卸载相同。
加载时,当存在驱动程序的任何实例时,函数
load/2
返回ok
。因此,如果驱动程序正在等待卸载(由于打开的端口),它只是将状态更改为不再需要卸载。load_driver/2 和 unload_driver/1 - 这些接口旨在用于当端口对没有 用户 加载的驱动程序打开时被认为是错误的情况。当最后一个 用户 调用
unload_driver/1
时,或当加载了驱动程序的最后一个进程死亡时,仍然打开的端口将被杀死,原因driver_unloaded
。函数名称
load_driver
和unload_driver
保留是为了向后兼容。
用于代码替换的加载和重新加载 - 如果在 Erlang 模拟器运行期间需要替换驱动程序代码,则可能会发生这种情况。实现驱动程序代码替换比 Beam 代码替换稍微繁琐一些,因为一个驱动程序不能同时作为“旧”代码和“新”代码加载。驱动程序的所有 用户 必须将其关闭(没有打开的端口),然后才能卸载旧代码并加载新代码。
卸载/加载作为一个原子操作完成,阻止系统中所有进程在进行中时使用有问题的驱动程序。
进行驱动程序代码替换的首选方法是让单个进程跟踪驱动程序。当进程启动时,加载驱动程序。当需要替换时,重新加载驱动程序。可能永远不会完成卸载,或者在进程退出时完成卸载。如果当需要代码替换时有多个 用户 加载了驱动程序,则在最后一个“其他”用户 卸载驱动程序之前,替换不会发生。
当已经正在进行重新加载时,要求重新加载始终是一个错误。使用高级函数,当多个 用户 加载了驱动程序时,要求重新加载也是一个错误。
为了简化驱动程序替换,请避免设计您的系统,使多个 用户 加载了驱动程序。
用于重新加载驱动程序的两个函数应与相应的加载函数一起使用,以支持关于打开端口的两种不同行为:
load/2 和 reload/2 - 当在最后一个打开的驱动程序端口关闭后进行重新加载时,使用此对函数。
由于
reload/2
等待重新加载发生,因此保持打开驱动程序端口(或保持驱动程序加载)的不良行为进程可能会导致无限等待重新加载。超时必须在要求重新加载的进程之外提供,或者通过结合驱动程序监视器使用低级接口try_load/3
提供。load_driver/2 和 reload_driver/2 - 当打开的驱动程序端口要被杀死,原因
driver_unloaded
以允许加载新的驱动程序代码时,使用此对函数。但是,如果另一个进程加载了驱动程序,则调用
reload_driver
会返回错误代码pending_process
。如前所述,推荐的设计是不允许“驱动程序重载器”以外的其他 用户 要求加载有问题的驱动程序。
另请参阅
摘要
函数
删除驱动程序监视器的方式与 ERTS 中的 erlang:demonitor/1
对进程监视器执行的操作非常相似。
采用由 load、unload 或 reload 函数返回的 ErrorDesc
,并返回描述错误或警告的字符串。
返回元组列表 {Tag, Value}
,其中 Tag
是信息项,Value
是使用此驱动程序名称和此标记调用 info/2
的结果。结果是一个元组列表,包含关于驱动程序的所有可用信息。
返回关于驱动程序的一个特定方面的信息。参数 Tag
指定要获取哪个方面的信息。返回的 Value
在不同的标记之间有所不同。
加载并链接动态驱动程序 Name
。Path
是包含驱动程序的目录的文件路径。Name
必须是可共享对象/动态库。具有不同 Path
参数的两个驱动程序不能以相同的名称加载。Name
是包含至少一个字符的字符串或原子。
其工作原理与 load/2
基本相同,但使用其他选项加载驱动程序。当要卸载驱动程序时,所有使用该驱动程序的端口都将因原因 driver_unloaded
而被杀死。
返回所有可用的驱动程序列表,包括(静态)链接的和动态加载的驱动程序。
创建驱动程序监视器,其工作方式与 ERTS 中的 erlang:monitor/2
对进程执行的操作类似。当驱动程序更改状态时,监视器会生成一个监视器消息,该消息会发送到调用进程。此函数返回的 MonitorRef
包含在发送的消息中。
从可能与先前使用的 Path
不同的 Path
重新加载名为 Name
的驱动程序。此函数在简介中描述的代码更改 场景
中使用。
其工作方式与 reload/2
完全相同,但适用于使用 load_driver/2
接口加载的驱动程序。
提供的控制比 load/2
/reload/2
和 load_driver/2
/reload_driver/2
接口更多。它永远不会等待与驱动程序相关的其他操作完成,而是立即返回驱动程序的状态,如下所示:
这是一个用于卸载(或减少引用计数)驱动程序的底层函数。它可以用于强制终止端口,其方式与驱动程序选项 kill_ports
隐式执行的方式非常相似。此外,它还可以触发监视器,原因可能是其他 用户 仍在加载驱动程序,或者是因为打开的端口正在使用该驱动程序。
卸载,或至少解除对名为 Name
的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,则所有剩余的使用该驱动程序的打开端口都将以 driver_unloaded
为原因被终止,并且该驱动程序最终将被卸载。
类型
函数
-spec demonitor(MonitorRef) -> ok when MonitorRef :: reference().
删除驱动程序监视器的方式与 ERTS 中的 erlang:demonitor/1
对进程监视器执行的操作非常相似。
有关如何创建驱动程序监视器的详细信息,请参阅 monitor/2
、try_load/3
和 try_unload/2
。
如果参数不是 reference/0
,则该函数会抛出 badarg
异常。
采用由 load、unload 或 reload 函数返回的 ErrorDesc
,并返回描述错误或警告的字符串。
注意
由于不同平台上的动态加载接口的特殊性,只有在出现错误的同一个 Erlang 虚拟机实例中(意味着相同的操作系统进程)调用 format_error/1 时,才能保证返回的字符串描述了正确的错误。
-spec info() -> AllInfoList when AllInfoList :: [DriverInfo], DriverInfo :: {DriverName, InfoList}, DriverName :: string(), InfoList :: [InfoItem], InfoItem :: {Tag :: atom(), Value :: term()}.
返回元组列表 {DriverName, InfoList}
,其中 InfoList
是调用该 DriverName
的 info/1
的结果。列表中仅包含动态链接的驱动程序。
-spec info(Name) -> InfoList when Name :: driver(), InfoList :: [InfoItem, ...], InfoItem :: {Tag :: atom(), Value :: term()}.
返回元组列表 {Tag, Value}
,其中 Tag
是信息项,Value
是使用此驱动程序名称和此标记调用 info/2
的结果。结果是一个元组列表,包含关于驱动程序的所有可用信息。
以下标签出现在列表中
processes
driver_options
port_count
linked_in_driver
permanent
awaiting_load
awaiting_unload
有关每个值的详细描述,请参阅 info/2
。
如果系统中不存在该驱动程序,则该函数会抛出 badarg
异常。
-spec info(Name, Tag) -> Value when Name :: driver(), Tag :: processes | driver_options | port_count | linked_in_driver | permanent | awaiting_load | awaiting_unload, Value :: term().
返回关于驱动程序的一个特定方面的信息。参数 Tag
指定要获取哪个方面的信息。返回的 Value
在不同的标记之间有所不同。
processes
- 返回包含特定驱动程序的 用户 的所有进程,以元组列表{pid(),integer() >= 0}
的形式表示,其中integer/0
表示进程pid/0
中的用户数量。driver_options
- 返回加载时提供的驱动程序选项列表,以及驱动程序在初始化期间设置的任何选项。唯一有效的选项是kill_ports
。port_count
- 返回使用该驱动程序的端口数(一个integer() >= 0
)。linked_in_driver
- 返回一个boolean/0
,如果该驱动程序是静态链接的,则为true
,否则为false
。permanent
- 返回一个boolean/0
,如果该驱动程序已将自身设为永久(并且不是静态链接的驱动程序),则为true
,否则为false
。awaiting_load
- 返回所有具有用于loading
的活动监视器的进程列表。每个进程都以{pid(),integer() >= 0}
的形式返回,其中integer/0
是进程pid/0
持有的监视器数量。awaiting_unload
- 返回所有具有用于unloading
的活动监视器的进程列表。每个进程都以{pid(),integer() >= 0}
的形式返回,其中integer/0
是进程pid/0
持有的监视器数量。
如果选项 linked_in_driver
或 permanent
返回 true
,则所有其他选项分别返回 linked_in_driver
或 permanent
。
如果系统中不存在该驱动程序或不支持该标签,则该函数会抛出 badarg
异常。
-spec load(Path, Name) -> ok | {error, ErrorDesc} when Path :: path(), Name :: driver(), ErrorDesc :: term().
加载并链接动态驱动程序 Name
。Path
是包含驱动程序的目录的文件路径。Name
必须是可共享对象/动态库。具有不同 Path
参数的两个驱动程序不能以相同的名称加载。Name
是包含至少一个字符的字符串或原子。
指定的 Name
应与位于指定为 Path
的目录中的动态可加载对象文件的文件名相对应,但不带扩展名(即,.so
)。驱动程序初始化例程中提供的驱动程序名称必须与文件名相对应,其方式与 Erlang 模块名称与 .beam
文件的名称相对应的方式非常相似。
如果驱动程序先前已卸载,但由于存在到该驱动程序的打开端口而仍存在,则调用 load/2
会停止卸载并保留该驱动程序(只要 Path
相同),并且返回 ok
。如果你真的想重新加载目标代码,请改用 reload/2
或底层接口 try_load/3
。另请参阅引言中有关加载/卸载的 不同场景
的描述。
如果多个进程尝试加载已加载的具有相同 Path
的驱动程序,或者如果同一进程尝试多次加载该驱动程序,则该函数返回 ok
。模拟器会跟踪 load/2
调用,因此在卸载驱动程序之前,必须从同一进程执行相应数量的 unload/2
调用。因此,应用程序可以在需要时安全地加载在进程或应用程序之间共享的驱动程序。它可以安全地卸载,而不会给系统的其他部分带来麻烦。
不允许加载多个具有相同名称但具有不同 Path
参数的驱动程序。
注意
Path
是按字面解释的,因此,同一驱动程序的所有加载器都必须指定相同的字面Path
字符串,尽管不同的路径可以指向文件系统中的同一目录(因为使用了相对路径和链接)。
成功时,该函数返回 ok
。失败时,返回值是 {error,ErrorDesc}
,其中 ErrorDesc
是一个不透明的术语,应由函数 format_error/1
将其转换为人类可读的形式。
为了更好地控制错误处理,请改用 try_load/3
接口。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
-spec load_driver(Path, Name) -> ok | {error, ErrorDesc} when Path :: path(), Name :: driver(), ErrorDesc :: term().
其工作原理与 load/2
基本相同,但使用其他选项加载驱动程序。当要卸载驱动程序时,所有使用该驱动程序的端口都将因原因 driver_unloaded
而被杀死。
不同 用户 的加载和卸载次数会影响驱动程序文件的加载和卸载。因此,只有在最后一个 用户 卸载驱动程序时,或者在加载驱动程序的最后一个进程退出时,才会发生端口终止。
此接口(或至少函数的名称)是为了向后兼容而保留的。在选项列表中使用 {driver_options,[kill_ports]}
的 try_load/3
会产生相同的端口终止效果。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
-spec loaded_drivers() -> {ok, Drivers} when Drivers :: [Driver], Driver :: string().
返回所有可用的驱动程序列表,包括(静态)链接的和动态加载的驱动程序。
出于历史原因,驱动程序名称以字符串列表而不是原子列表的形式返回。
有关驱动程序的更多信息,请参阅 info
。
-spec monitor(Tag, Item) -> MonitorRef when Tag :: driver, Item :: {Name, When}, Name :: driver(), When :: loaded | unloaded | unloaded_only, MonitorRef :: reference().
创建驱动程序监视器,其工作方式与 ERTS 中的 erlang:monitor/2
对进程执行的操作类似。当驱动程序更改状态时,监视器会生成一个监视器消息,该消息会发送到调用进程。此函数返回的 MonitorRef
包含在发送的消息中。
与进程监视器一样,每个驱动程序监视器集仅生成一条消息。发送消息后,监视器将被“销毁”,因此不需要调用 demonitor/1
。
MonitorRef
也可以在后续调用 demonitor/1
中使用,以删除监视器。
该函数接受以下参数
Tag
- 监视器标签始终为driver
,因为此函数只能用于创建驱动程序监视器。将来,驱动程序监视器将与进程监视器集成,因此必须指定此参数以保持一致性。Item
- 参数Item
指定要监视的驱动程序(驱动程序名称)以及要监视的状态更改。该参数是一个二元元组,其第一个元素是驱动程序名称,第二个元素是以下之一loaded
- 在重新加载驱动程序(或如果正在加载则加载)时通知。仅监视正在加载或重新加载的驱动程序才有意义。无法监视未来用于加载的驱动程序名称。这只会立即发送一个DOWN
消息。因此,当由函数try_load/3
触发时,监视加载最有用,因为监视器是在驱动程序处于这种挂起状态时创建的。设置用于
loading
的驱动程序监视器最终会导致发送以下消息之一{'UP', reference(), driver, Name, loaded}
- 如果驱动程序已加载且没有重新加载挂起,则会立即发送此消息;或者如果重新加载挂起,则在执行重新加载时发送此消息。用户 应该知道在创建加载监视器之前是否需要重新加载。
{'UP', reference(), driver, Name, permanent}
- 如果预期会重新加载,但(旧的)驱动程序在重新加载之前将自己设为永久,则会发送此消息。如果驱动程序在尝试创建监视器时是永久的或静态链接的,也会发送此消息。{'DOWN', reference(), driver, Name, load_cancelled}
- 如果重新加载正在进行中,但请求的 用户 通过死亡或再次调用try_unload/2
(或unload/1
/unload_driver/1
)来取消了重新加载,则会收到此消息。{'DOWN', reference(), driver, Name, {load_failure, Failure}}
- 如果正在进行重新加载,但由于某种原因加载失败,则会收到此消息。Failure
项是try_load/3
可能返回的错误之一。可以将错误项传递给format_error/1
以转换为人类可读的形式。请注意,转换必须在与检测到错误的同一个正在运行的 Erlang 虚拟机中进行。
unloaded
- 监视驱动程序何时卸载。如果监视系统不存在的驱动程序,则会立即收到该驱动程序已卸载的通知。不能保证该驱动程序曾经被加载过。驱动程序卸载监视最终会发送以下消息之一
{'DOWN', reference(), driver, Name, unloaded}
- 受监视的驱动程序实例现在已卸载。由于卸载可能是reload/2
请求的结果,因此当收到此消息时,驱动程序可能会再次被加载。{'UP', reference(), driver, Name, unload_cancelled}
- 如果预期会卸载,但在驱动程序等待所有端口关闭时,出现了该驱动程序的新 用户,则会发送此消息,并且卸载被取消。如果从
try_unload/2
为该驱动程序的最后一个 用户 返回了{ok, pending_driver}
,然后从调用try_load/3
返回了{ok, already_loaded}
,则会出现此消息。如果 真的 想要监视驱动程序何时卸载,则此消息会扭曲画面,因为没有进行任何卸载。选项
unloaded_only
创建一个类似于unloaded
监视器的监视器,但绝不会导致此消息的发送。{'UP', reference(), driver, Name, permanent}
- 如果预期会卸载,但驱动程序在卸载前将自身设置为永久状态,则会发送此消息。如果尝试监视永久或静态链接的驱动程序,也会发送此消息。
unloaded_only
- 以unloaded_only
形式创建的监视器的行为与以unloaded
形式创建的监视器完全相同,唯一的区别是永远不会发送{'UP', reference(), driver, Name, unload_cancelled}
消息,而是监视器会一直持续到驱动程序 真正 被卸载。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
-spec reload(Path, Name) -> ok | {error, ErrorDesc} when Path :: path(), Name :: driver(), ErrorDesc :: pending_process | OpaqueError, OpaqueError :: term().
从可能与先前使用的 Path
不同的 Path
重新加载名为 Name
的驱动程序。此函数在简介中描述的代码更改 场景
中使用。
如果此驱动程序有其他 用户,则该函数返回 {error, pending_process}
,但如果没有其他用户,则函数调用会一直挂起,直到所有打开的端口都关闭。
注意
避免将多个 用户 与驱动程序重新加载请求混合使用。
为避免在打开的端口上挂起,请改用函数 try_load/3
。
Name
和 Path
参数的含义与调用普通函数 load/2
时完全相同。
如果成功,该函数将返回 ok
。如果失败,该函数将返回一个不透明的错误,除了前面描述的 pending_process
错误。不透明的错误将由函数 format_error/1
转换为人类可读的形式。
为了更好地控制错误处理,请改用 try_load/3
接口。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
-spec reload_driver(Path, Name) -> ok | {error, ErrorDesc} when Path :: path(), Name :: driver(), ErrorDesc :: pending_process | OpaqueError, OpaqueError :: term().
其工作方式与 reload/2
完全相同,但适用于使用 load_driver/2
接口加载的驱动程序。
由于此接口意味着当最后一个用户消失时端口会被杀死,因此该函数不会挂起等待端口关闭。
有关更多详细信息,请参阅此模块描述中的 scenarios
以及 reload/2
的函数描述。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
-spec try_load(Path, Name, OptionList) -> {ok, Status} | {ok, PendingStatus, Ref} | {error, ErrorDesc} when Path :: path(), Name :: driver(), OptionList :: [Option], Option :: {driver_options, DriverOptionList} | {monitor, MonitorOption} | {reload, ReloadOption}, DriverOptionList :: [DriverOption], DriverOption :: kill_ports, MonitorOption :: pending_driver | pending, ReloadOption :: pending_driver | pending, Status :: loaded | already_loaded | PendingStatus, PendingStatus :: pending_driver | pending_process, Ref :: reference(), ErrorDesc :: ErrorAtom | OpaqueError, ErrorAtom :: linked_in_driver | inconsistent | permanent | not_loaded_by_this_process | not_loaded | pending_reload | pending_process, OpaqueError :: term().
提供的控制比 load/2
/reload/2
和 load_driver/2
/reload_driver/2
接口更多。它永远不会等待与驱动程序相关的其他操作完成,而是立即返回驱动程序的状态,如下所示:
{ok, loaded}
- 驱动程序已加载并且可以立即使用。{ok, already_loaded}
- 驱动程序已由另一个进程加载,或者正在被活动端口使用,或者两者兼有。您的加载已注册,并且预计将来会进行相应的try_unload
。{ok, pending_driver}
或{ok, pending_driver, reference()}
- 加载请求已注册,但由于驱动程序的早期实例仍在等待卸载(打开的端口正在使用它),因此加载被延迟。尽管如此,当您完成使用驱动程序后,预计仍会卸载。此返回值 主要 在使用选项{reload,pending_driver}
或{reload,pending}
时发生,但 可能 在另一个 用户 并行卸载驱动程序并且驱动程序选项kill_ports
设置时发生。换句话说,始终需要处理此返回值。{ok, pending_process}
或{ok, pending_process, reference()}
- 加载请求已注册,但由于驱动程序的早期实例仍在等待另一个 用户 卸载(不仅是通过端口,在这种情况下会返回{ok,pending_driver}
),因此加载被延迟。尽管如此,当您完成使用驱动程序后,预计仍会卸载。此返回值 仅 在使用选项{reload,pending}
时发生。
当函数返回 {ok, pending_driver}
或 {ok, pending_process}
时,可以使用选项 {monitor, MonitorOption}
获取有关驱动程序何时 实际 加载的信息。
当请求监视时,并且会返回相应的 {ok, pending_driver}
或 {ok, pending_process}
时,该函数将返回一个元组 {ok, PendingStatus, reference()}
,并且进程稍后会在驱动程序加载时收到监视消息。预期的监视消息在 monitor/2
的函数描述中进行了描述。
注意
在加载的情况下,监视不仅可以通过使用选项
{reload, ReloadOption}
触发,也可以在加载错误是瞬态的特殊情况下触发。因此,{monitor, pending_driver}
基本上要在 所有 实际情况下使用。
该函数接受以下参数
Path
- 驱动程序对象文件所在目录的文件系统路径。对象文件的文件名(减去扩展名)必须与驱动程序名称(在参数Name
中使用)相对应,并且驱动程序必须使用相同的名称标识自身。Path
可以作为 iolist() 提供,这意味着它可以是其他iolist/0
、字符(8 位整数)或二进制文件的列表,所有这些都将被展平为字符序列。(可能已展平的)
Path
参数在整个系统中必须保持一致。一个驱动程序要由所有 用户 使用相同的 字面Path
加载。唯一的例外是当请求 重新加载 时,在这种情况下,可以以不同的方式指定Path
。请注意,如果使用reload
选项更改了Path
,则所有以后尝试加载该驱动程序的 用户 都需要使用 新的Path
。这是在运行的系统中仅有 一个 驱动程序的加载程序,希望升级它的另一个原因。Name
- 此参数是在 ERTS 中后续调用函数erlang:open_port
时要使用的驱动程序名称。该名称可以指定为iolist/0
或atom/0
。加载时指定的名称用于查找对象文件(在Path
和系统隐含的扩展名后缀(即.so
)的帮助下)。驱动程序用于标识自身的名称也必须与此Name
参数一致,就像 Beam 文件的模块名称与其文件名非常匹配一样。OptionList
- 可以指定一些选项来控制加载操作。这些选项以双元组列表的形式指定。元组具有以下值和含义{driver_options, DriverOptionList}
- 这是为了提供更改其一般行为并在整个生命周期中“粘附”到驱动程序的选项。指定的驱动程序名称的驱动程序选项始终必须保持一致,即使在重新加载驱动程序时也是如此,这意味着它们与名称一样是驱动程序的一部分。
唯一允许的驱动程序选项是
kill_ports
,这意味着当没有任何进程再加载该驱动程序时,所有打开到该驱动程序的端口都会以退出原因driver_unloaded
被杀死。这种情况发生在最后一个 用户 调用try_unload/2
时,或者当最后一个加载该驱动程序的进程退出时。{monitor, MonitorOption}
-MonitorOption
告诉try_load/3
在特定条件下触发驱动程序监视器。当触发监视器时,该函数将返回一个三元组{ok, PendingStatus, reference()}
,其中reference/0
是驱动程序监视器的监视器引用。只能指定一个
MonitorOption
。它是以下之一- 原子
pending
,表示每当加载操作延迟时都将创建监视器。 - 原子
pending_driver
,表示每当由于打开了未使用的驱动程序的端口而导致操作延迟时,都会创建监视器。
选项
pending_driver
的用处不大,但为了完整性而存在,因为明确定义了哪些重新加载选项可能导致哪些延迟。但是,如果存在,最好使用与ReloadOption
相同的MonitorOption
。即使不请求重新加载,指定
monitor
选项仍然很有用,因为强制卸载(驱动程序选项kill_ports
或try_unload/2
的选项kill_ports
)会触发一个瞬态状态,在该状态下,在所有正在关闭的端口都关闭之前,无法执行驱动程序加载。因此,由于try_unload
在几乎所有情况下都可以返回{ok, pending_driver}
,因此在生产代码中始终至少指定{monitor, pending_driver}
(请参阅前面的监控讨论)。- 原子
{reload, ReloadOption}
- 此选项用于从磁盘重新加载驱动程序,最常见的情况是在代码升级场景中。拥有reload
选项还意味着参数Path
不需要与驱动程序的早期加载保持一致。要重新加载驱动程序,该进程之前必须已加载该驱动程序,也就是说,该进程中必须有该驱动程序的活动用户。
reload
选项可以是以下之一pending
- 使用原子pending
,会为任何驱动程序请求重新加载,并且当所有打开到该驱动程序的端口都关闭时生效。在这种情况下,即使仍然有用户已加载该驱动程序,也会进行驱动程序替换。该选项还会触发端口终止(如果使用了驱动程序选项
kill_ports
),即使有等待的用户,使其可用于强制驱动程序替换,但将大部分责任放在驱动程序的用户身上。由于不希望在代码更改正在进行时其他用户加载驱动程序,因此很少使用 pending 选项。pending_driver
- 此选项更有用。如果驱动程序没有被其他用户加载,但是驱动程序打开了端口,则会在此处将重新加载排队,在这种情况下,将返回{ok, pending_driver}
(建议使用monitor
选项)。
如果驱动程序已卸载(系统中不存在),则会返回错误代码
not_loaded
。选项reload
旨在用于用户已提前加载驱动程序的情况。
该函数可以返回许多错误,有些错误只能在给定的选项组合下返回。
某些错误是不透明的,只能通过将它们传递给函数 format_error/1
来解释,但有些可以直接解释
{error,linked_in_driver}
- 具有指定名称的驱动程序是 Erlang 静态链接的驱动程序,无法使用此 API 操作。{error,inconsistent}
- 该驱动程序已加载了其他DriverOptionList
或不同的字面值Path
参数。即使指定了
reload
选项,如果DriverOptionList
与当前的不同,也会发生这种情况。{error, permanent}
- 该驱动程序已请求自身成为永久的,使其行为类似于 Erlang 链接的驱动程序,并且无法再使用此 API 操作。{error, pending_process}
- 当指定选项{reload, pending_driver}
时,该驱动程序由其他 用户 加载。{error, pending_reload}
- 当指定选项{reload, ReloadOption}
时,另一个 用户 已请求重新加载驱动程序。{error, not_loaded_by_this_process}
- 当指定reload
选项时出现。驱动程序Name
存在于系统中,但此进程中没有它的用户。{error, not_loaded}
- 当指定reload
选项时出现。驱动程序Name
不在系统中。只有此进程加载的驱动程序才能重新加载。
所有其他错误代码都由函数 format_error/1
转换。请注意,对 format_error
的调用必须从检测到错误的 Erlang 虚拟机同一正在运行的实例执行,因为错误值存在与系统相关的行为。
如果参数或选项格式不正确,该函数将抛出 badarg
异常。
-spec try_unload(Name, OptionList) -> {ok, Status} | {ok, PendingStatus, Ref} | {error, ErrorAtom} when Name :: driver(), OptionList :: [Option], Option :: {monitor, MonitorOption} | kill_ports, MonitorOption :: pending_driver | pending, Status :: unloaded | PendingStatus, PendingStatus :: pending_driver | pending_process, Ref :: reference(), ErrorAtom :: linked_in_driver | not_loaded | not_loaded_by_this_process | permanent.
这是一个用于卸载(或减少引用计数)驱动程序的底层函数。它可以用于强制终止端口,其方式与驱动程序选项 kill_ports
隐式执行的方式非常相似。此外,它还可以触发监视器,原因可能是其他 用户 仍在加载驱动程序,或者是因为打开的端口正在使用该驱动程序。
卸载可以描述为告知仿真器,此特定进程中代码的此特定部分(即此用户)不再需要该驱动程序的过程。如果没有任何其他用户,则可以触发驱动程序的卸载,在这种情况下,驱动程序名称将从系统中消失,并且(如果可能)会回收驱动程序可执行代码占用的内存。
如果驱动程序设置了选项 kill_ports
,或者如果将 kill_ports
指定为此函数的选项,则当最后一个用户完成卸载时,所有使用此驱动程序的待处理端口都将被终止。如果未涉及端口终止,并且存在打开的端口,则卸载将延迟,直到没有更多的打开端口使用该驱动程序。在这种情况下,如果另一个用户(甚至该用户)在驱动程序卸载之前再次加载该驱动程序,则永远不会发生卸载。
为了允许用户请求卸载以等待实际卸载,可以指定 monitor
触发器,其方式与加载时非常相似。但是,由于此函数的用户很少对递减引用计数感兴趣,因此很少需要监控。
注意
如果使用了选项
kill_ports
,则监控触发至关重要,因为不能保证端口在驱动程序卸载之前被终止。因此,必须至少为pending_driver
情况触发监控。
当使用函数 monitor/2
的选项 unloaded
时,预期可能出现的监控消息是相同的。
该函数在成功时返回以下状态之一
{ok, unloaded}
- 驱动程序已立即卸载,这意味着驱动程序名称现在可以供其他驱动程序使用,并且如果底层操作系统允许,则驱动程序对象代码占用的内存现在已被回收。只有在没有使用它的打开端口,并且没有更多的用户需要加载它时,才能卸载驱动程序。
{ok, pending_driver}
或{ok, pending_driver, reference()}
- 表示此调用从驱动程序中删除了最后一个用户,但仍有打开的端口正在使用它。当所有端口都关闭并且没有新的用户到达时,将重新加载驱动程序,并回收名称和内存。即使使用了选项
kill_ports
,此返回值也是有效的,因为终止端口可能是一个不会立即完成的过程。但是,这种情况是暂时的。监控对于检测驱动程序何时真正卸载始终很有用。{ok, pending_process}
或{ok, pending_process, reference()}
- 卸载请求已注册,但其他用户仍持有该驱动程序。请注意,术语pending_process
可以指正在运行的进程;在同一进程中可能有多个用户。如果该调用只是为了通知仿真器您不再需要该驱动程序,则这是一个正常、健康的返回值。这是引言中描述的最常见
场景
中最常见的返回值。
该函数接受以下参数
OptionList
- 参数OptionList
可用于指定在某些情况下关于端口和触发监控的特定行为kill_ports
- 如果您是驱动程序的最后一个用户,则强制终止使用此驱动程序打开的所有端口,退出原因为driver_unloaded
。如果其他用户加载了该驱动程序,则此选项无效。
为了在最后一个用户卸载时获得终止端口的一致行为,请在加载驱动程序时改用驱动程序选项
kill_ports
。{monitor, MonitorOption}
- 如果MonitorOption
中指定的条件为真,则创建驱动程序监视器。有效选项为pending_driver
- 如果返回值将为{ok, pending_driver}
,则创建驱动程序监视器。pending
- 如果返回值为{ok, pending_driver}
或{ok, pending_process}
,则创建监视器。
pending_driver
MonitorOption
是迄今为止最有用的。必须使用它来确保驱动程序确实已卸载,并且在使用了选项kill_ports
或驱动程序可以使用驱动程序选项kill_ports
加载时,端口已关闭。在调用
try_unload
时使用监视器触发器可确保在执行卸载之前添加监视器,这意味着始终会正确触发监视器,如果单独调用monitor/2
,则情况并非如此。
该函数可以返回以下错误条件,所有条件都已明确指定(没有不透明的值)
{error, linked_in_driver}
- 您试图卸载 Erlang 静态链接的驱动程序,该驱动程序无法使用此接口进行操作(并且根本无法卸载)。{error, not_loaded}
- 驱动程序Name
不存在于系统中。{error, not_loaded_by_this_process}
- 驱动程序Name
存在于系统中,但是此进程中没有它的用户。作为一种特殊情况,如果根本没有驱动程序的用户,则可以从没有对
try_load/3
进行相应调用的进程中卸载驱动程序,如果包含最后一个用户的进程死亡,则会发生这种情况。{error, permanent}
- 该驱动程序已使其自身成为永久的,在这种情况下,它将无法再通过此接口进行操作(很像静态链接的驱动程序)。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
卸载,或至少解除对名为 Name
的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,并且没有更多打开的端口使用该驱动程序,则该驱动程序将被卸载。否则,卸载将延迟到所有端口关闭且没有 用户 剩余为止。
如果驱动程序还有其他用户,则只会减少该驱动程序的引用计数,以便调用方不再被视为该驱动程序的用户。有关使用场景,请参阅本模块开头的description
。
返回的 ErrorDesc
是一个不透明的值,将进一步传递给函数 format_error/1
。 如果要对操作进行更多控制,请使用 try_unload/2
接口。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。
卸载,或至少解除对名为 Name
的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,则所有剩余的使用该驱动程序的打开端口都将以 driver_unloaded
为原因被终止,并且该驱动程序最终将被卸载。
如果驱动程序还有其他用户,则只会减少该驱动程序的引用计数,以便调用方不再被视为该驱动程序的用户。有关使用场景,请参阅本模块开头的description
。
返回的 ErrorDesc
是一个不透明的值,将进一步传递给函数 format_error/1
。 如果要对操作进行更多控制,请使用 try_unload/2
接口。
如果未按此处所述指定参数,则该函数会抛出 badarg
异常。