查看源码 erl_ddll (内核 v10.2)

动态驱动程序加载器和链接器。

此模块提供了一个接口,用于在运行时加载和卸载Erlang 链接驱动程序

注意

这是一个大型参考文档。对于此模块的偶尔使用,以及对于大多数实际应用,函数 load/2unload/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_driverunload_driver 保留是为了向后兼容。

  • 用于代码替换的加载和重新加载 - 如果在 Erlang 模拟器运行期间需要替换驱动程序代码,则可能会发生这种情况。实现驱动程序代码替换比 Beam 代码替换稍微繁琐一些,因为一个驱动程序不能同时作为“旧”代码和“新”代码加载。驱动程序的所有 用户 必须将其关闭(没有打开的端口),然后才能卸载旧代码并加载新代码。

    卸载/加载作为一个原子操作完成,阻止系统中所有进程在进行中时使用有问题的驱动程序。

    进行驱动程序代码替换的首选方法是让单个进程跟踪驱动程序。当进程启动时,加载驱动程序。当需要替换时,重新加载驱动程序。可能永远不会完成卸载,或者在进程退出时完成卸载。如果当需要代码替换时有多个 用户 加载了驱动程序,则在最后一个“其他”用户 卸载驱动程序之前,替换不会发生。

    当已经正在进行重新加载时,要求重新加载始终是一个错误。使用高级函数,当多个 用户 加载了驱动程序时,要求重新加载也是一个错误。

    为了简化驱动程序替换,请避免设计您的系统,使多个 用户 加载了驱动程序。

    用于重新加载驱动程序的两个函数应与相应的加载函数一起使用,以支持关于打开端口的两种不同行为:

    • load/2 和 reload/2 - 当在最后一个打开的驱动程序端口关闭后进行重新加载时,使用此对函数。

      由于 reload/2 等待重新加载发生,因此保持打开驱动程序端口(或保持驱动程序加载)的不良行为进程可能会导致无限等待重新加载。超时必须在要求重新加载的进程之外提供,或者通过结合驱动程序监视器使用低级接口 try_load/3 提供。

    • load_driver/2 和 reload_driver/2 - 当打开的驱动程序端口要被杀死,原因 driver_unloaded 以允许加载新的驱动程序代码时,使用此对函数。

      但是,如果另一个进程加载了驱动程序,则调用 reload_driver 会返回错误代码 pending_process。如前所述,推荐的设计是不允许“驱动程序重载器”以外的其他 用户 要求加载有问题的驱动程序。

另请参阅

erl_driver(4)driver_entry(4)

摘要

函数

删除驱动程序监视器的方式与 ERTS 中的 erlang:demonitor/1 对进程监视器执行的操作非常相似。

采用由 load、unload 或 reload 函数返回的 ErrorDesc,并返回描述错误或警告的字符串。

返回元组列表 {DriverName, InfoList},其中 InfoList 是调用该 DriverNameinfo/1 的结果。列表中仅包含动态链接的驱动程序。

返回元组列表 {Tag, Value},其中 Tag 是信息项,Value 是使用此驱动程序名称和此标记调用 info/2 的结果。结果是一个元组列表,包含关于驱动程序的所有可用信息。

返回关于驱动程序的一个特定方面的信息。参数 Tag 指定要获取哪个方面的信息。返回的 Value 在不同的标记之间有所不同。

加载并链接动态驱动程序 NamePath 是包含驱动程序的目录的文件路径。Name 必须是可共享对象/动态库。具有不同 Path 参数的两个驱动程序不能以相同的名称加载。Name 是包含至少一个字符的字符串或原子。

其工作原理与 load/2 基本相同,但使用其他选项加载驱动程序。当要卸载驱动程序时,所有使用该驱动程序的端口都将因原因 driver_unloaded 而被杀死。

返回所有可用的驱动程序列表,包括(静态)链接的和动态加载的驱动程序。

创建驱动程序监视器,其工作方式与 ERTS 中的 erlang:monitor/2 对进程执行的操作类似。当驱动程序更改状态时,监视器会生成一个监视器消息,该消息会发送到调用进程。此函数返回的 MonitorRef 包含在发送的消息中。

从可能与先前使用的 Path 不同的 Path 重新加载名为 Name 的驱动程序。此函数在简介中描述的代码更改 场景 中使用。

其工作方式与 reload/2 完全相同,但适用于使用 load_driver/2 接口加载的驱动程序。

提供的控制比 load/2/reload/2load_driver/2/reload_driver/2 接口更多。它永远不会等待与驱动程序相关的其他操作完成,而是立即返回驱动程序的状态,如下所示:

这是一个用于卸载(或减少引用计数)驱动程序的底层函数。它可以用于强制终止端口,其方式与驱动程序选项 kill_ports 隐式执行的方式非常相似。此外,它还可以触发监视器,原因可能是其他 用户 仍在加载驱动程序,或者是因为打开的端口正在使用该驱动程序。

卸载,或至少解除对名为 Name 的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,并且没有更多打开的端口使用该驱动程序,则该驱动程序将被卸载。否则,卸载将延迟到所有端口关闭且没有 用户 剩余为止。

卸载,或至少解除对名为 Name 的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,则所有剩余的使用该驱动程序的打开端口都将以 driver_unloaded 为原因被终止,并且该驱动程序最终将被卸载。

类型

链接到此类型

driver()

查看源代码 (未导出)
-type driver() :: iolist() | atom().
-type path() :: string() | atom().

函数

-spec demonitor(MonitorRef) -> ok when MonitorRef :: reference().

删除驱动程序监视器的方式与 ERTS 中的 erlang:demonitor/1 对进程监视器执行的操作非常相似。

有关如何创建驱动程序监视器的详细信息,请参阅 monitor/2try_load/3try_unload/2

如果参数不是 reference/0,则该函数会抛出 badarg 异常。

链接到此函数

format_error(ErrorDesc)

查看源代码
-spec format_error(ErrorDesc) -> string() when ErrorDesc :: term().

采用由 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 是调用该 DriverNameinfo/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_driverpermanent 返回 true,则所有其他选项分别返回 linked_in_driverpermanent

如果系统中不存在该驱动程序或不支持该标签,则该函数会抛出 badarg 异常。

-spec load(Path, Name) -> ok | {error, ErrorDesc}
              when Path :: path(), Name :: driver(), ErrorDesc :: term().

加载并链接动态驱动程序 NamePath 是包含驱动程序的目录的文件路径。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 异常。

链接到此函数

load_driver(Path, Name)

查看源代码
-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

NamePath 参数的含义与调用普通函数 load/2 时完全相同。

如果成功,该函数将返回 ok。如果失败,该函数将返回一个不透明的错误,除了前面描述的 pending_process 错误。不透明的错误将由函数 format_error/1 转换为人类可读的形式。

为了更好地控制错误处理,请改用 try_load/3 接口。

如果未按此处所述指定参数,则该函数会抛出 badarg 异常。

链接到此函数

reload_driver(Path, Name)

查看源代码
-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 异常。

链接到此函数

try_load(Path, Name, OptionList)

查看源代码
-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/2load_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/0atom/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_portstry_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 异常。

链接到此函数

try_unload(Name, OptionList)

查看源代码
-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 可以指正在运行的进程;在同一进程中可能有多个用户

    如果该调用只是为了通知仿真器您不再需要该驱动程序,则这是一个正常、健康的返回值。这是引言中描述的最常见场景中最常见的返回值。

该函数接受以下参数

  • Name - Name 是要卸载的驱动程序的名称。该名称可以指定为 iolist/0atom/0

  • 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 异常。

-spec unload(Name) -> ok | {error, ErrorDesc} when Name :: driver(), ErrorDesc :: term().

卸载,或至少解除对名为 Name 的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,并且没有更多打开的端口使用该驱动程序,则该驱动程序将被卸载。否则,卸载将延迟到所有端口关闭且没有 用户 剩余为止。

如果驱动程序还有其他用户,则只会减少该驱动程序的引用计数,以便调用方不再被视为该驱动程序的用户。有关使用场景,请参阅本模块开头的description

返回的 ErrorDesc 是一个不透明的值,将进一步传递给函数 format_error/1。 如果要对操作进行更多控制,请使用 try_unload/2 接口。

如果未按此处所述指定参数,则该函数会抛出 badarg 异常。

-spec unload_driver(Name) -> ok | {error, ErrorDesc} when Name :: driver(), ErrorDesc :: term().

卸载,或至少解除对名为 Name 的驱动程序的引用。如果调用者是驱动程序的最后一个 用户,则所有剩余的使用该驱动程序的打开端口都将以 driver_unloaded 为原因被终止,并且该驱动程序最终将被卸载。

如果驱动程序还有其他用户,则只会减少该驱动程序的引用计数,以便调用方不再被视为该驱动程序的用户。有关使用场景,请参阅本模块开头的description

返回的 ErrorDesc 是一个不透明的值,将进一步传递给函数 format_error/1。 如果要对操作进行更多控制,请使用 try_unload/2 接口。

如果未按此处所述指定参数,则该函数会抛出 badarg 异常。