16 编译和代码加载
代码如何编译和加载不是语言问题,而是系统相关的。本节介绍 Erlang/OTP 中的编译和代码加载,并引用相关文档部分。
16.1 编译
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
16.2 代码加载
目标代码必须**加载**到 Erlang 运行时系统中。这由**代码服务器**处理,请参见内核中的code(3) 手册页。
代码服务器根据代码加载策略加载代码,该策略可以是**交互式**(默认)或**嵌入式**。在交互模式下,代码在**代码路径**中搜索,并在首次引用时加载。在嵌入式模式下,代码根据**引导脚本**在启动时加载。这在 系统原理 中有所描述。
16.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 语法。
16.4 模块加载时运行函数
-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 的调用失败,则将卸载该模块,并向错误加载器发送警告报告。