16  编译和代码加载

16 编译和代码加载

代码如何编译和加载不是语言问题,而是系统相关的。本节介绍 Erlang/OTP 中的编译和代码加载,并引用相关文档部分。

Erlang 程序必须**编译**为目标代码。编译器可以生成一个包含目标代码的新文件。当前运行目标代码的抽象机器称为 BEAM,因此目标文件的后缀为.beam。编译器还可以生成可以直接加载的二进制文件。

编译器位于compile 模块中(请参见编译器中的compile(3) 手册页)。

compile:file(Module)
compile:file(Module, Options)

Erlang shell 理解命令c(Module),它同时编译和加载Module

还有一个make 模块,它提供了一组类似于 UNIX 类型 Make 函数的功能,请参见工具中的make(3) 手册页。

也可以从操作系统提示符访问编译器,请参见 ERTS 中的erl(1) 手册页。

% erl -compile Module1...ModuleN
% erl -make

erlc 程序提供了一种更便捷的方式从 shell 中编译模块,请参见 ERTS 中的erlc(1) 手册页。它理解许多可以用来定义宏、添加包含文件搜索路径等等的标志。

% erlc <flags> File1.erl...FileN.erl

目标代码必须**加载**到 Erlang 运行时系统中。这由**代码服务器**处理,请参见内核中的code(3) 手册页。

代码服务器根据代码加载策略加载代码,该策略可以是**交互式**(默认)或**嵌入式**。在交互模式下,代码在**代码路径**中搜索,并在首次引用时加载。在嵌入式模式下,代码根据**引导脚本**在启动时加载。这在 系统原理 中有所描述。

Erlang 支持在运行系统中更改代码。代码替换在模块级别完成。

模块的代码可以在系统中以两种变体存在:**当前**和**旧的**。当模块第一次加载到系统中时,该代码变为“当前”。如果随后加载该模块的新实例,则先前实例的代码变为“旧的”,而新实例变为“当前”。

旧的和当前的代码都是有效的,可以并发执行。完全限定的函数调用始终引用当前代码。旧的代码仍然可以执行,因为进程可能仍在旧的代码中存在。

如果加载该模块的第三个实例,代码服务器将删除(清除)旧的代码,并终止所有停留在旧代码中的进程。然后,第三个实例变为“当前”,而先前当前的代码变为“旧的”。

要从旧的代码切换到当前的代码,进程必须进行完全限定的函数调用。

示例

-module(m).
-export([loop/0]).

loop() ->
    receive
        code_switch ->
            m:loop();
        Msg ->
            ...
            loop()
    end.

要使进程更改代码,请向其发送消息code_switch。然后,进程将对m:loop() 进行完全限定的调用,并切换到当前代码。请注意,m:loop/0 必须导出。

对于函的代码替换,请使用fun Module:FunctionName/Arity 语法。

-on_load() 指令命名一个函数,该函数在加载模块时自动运行。

它的语法如下

-on_load(Name/0).

不需要导出该函数。它在新建的进程中调用(该进程在函数返回后立即终止)。

如果模块要成为模块的新当前代码并可调用,则该函数必须返回ok

返回任何其他值或生成异常会导致卸载新代码。如果返回值不是原子,则会向错误日志发送警告错误报告。

如果模块已经存在当前代码,则该代码将保持当前状态,并且可以在on_load 函数返回之前被调用。如果on_load 函数失败,当前代码(如果有)将保持当前状态。如果模块没有当前代码,则在on_load 函数完成之前对模块进行任何外部调用的进程将被挂起,直到on_load 函数完成。

更改

在 Erlang/OTP 19 之前,如果on_load 函数失败,任何先前当前的代码将变为旧的,这实际上会导致系统没有任何工作和可访问的模块实例。

在嵌入式模式下,首先加载所有模块。然后调用所有on_load 函数。除非所有on_load 函数都返回ok,否则系统将终止。

示例

-module(m).
-on_load(load_my_nifs/0).

load_my_nifs() ->
    NifPath = ...,    %Set up the path to the NIF library.
    Info = ...,       %Initialize the Info term
    erlang:load_nif(NifPath, Info).

如果对erlang:load_nif/2 的调用失败,则将卸载该模块,并向错误加载器发送警告报告。