版本 5.7.1
翻译:krh2001
1 系统原理
1.1 启动系统
一个Erlang 运行时系统可通过命令erl 来启动:
% erl
Erlang (BEAM) emulator version 5.2.3.5 [hipe] [threads:0]
Eshell V5.2.3.5 (abort with ^G)
1>
erl 解释执行从命令行方式输入的一到几行参数,参见erl(1) 。有几个命令也在本节中被描述。
通过调用函数init:get_argument(Key), 或者init:get_arguments() ,应用程序也能够访问从命令行执行命令所产生的变量的数值。
1.2 重启和停止系统
通过调用函数halt/0 ,1 可以终止运行时系统。参见erlang(3) 。
模块init 包含了用来再次启动(restarting) ,重新启动(rebooting) 和停止运行时系统的几个函数 。参见init(3) 。
init:restart()
init:reboot()
init:stop()
同时,如果Erlang shell 被终止,运行时系统也将终止。
1.3 引导脚本
运行时系统使用一个引导脚本 (boot script ) 来启动。引导脚本中包含一些指令,指出哪些代码被加载,哪些进程和程序被启动。
引导脚本文件具有扩展名:“.script ”。运行时系统使用的是一个编译过的二进制版本的引导脚本。这个二进制引导脚本 (binary boot script )文件具有扩展名:“.boot ”。
通过命令行参数-boot 来指出哪一个引导脚本将被使用。扩展名“.boot ”应该省略。例如,使用引导脚本“start_all.boot ”来启动:
% erl -boot start_all
如果没有指定一个引导脚本,缺省的引导脚本为“ROOT/bin/start ”,参见后面的缺省引导脚本的说明。
命令行参数-init_debug 将使初始化进程在解释执行引导脚本时输出一些调试信息:
% erl -init_debug
{progress,preloaded}
{progress,kernel_load_completed}
{progress,modules_loaded}
{start,heart}
{start,error_logger}
...
关于引导脚本的详细的语法和内容可参见script(4) 。
1.3.1 缺省的引导脚本
Erlang/OTP 自带有两个引导脚本:
start_clean.boot
加载和启动内核(Kernel )和标准库(STLLIB )应用程序。
start_sasl.boot
Loads the code for and starts the applications Kernel, STDLIB and SASL. 加载和启动内核(Kernel ),标准库(STDLIB )和SALS (System Architecture Support Libraries, 适合产品化的环境,支持错误日志,过载保护等)应用程序。
start_clear 和start_sasl 哪一个将被缺省使用取决于用户在安装Erlang/OTP 时的选择,当安装程序询问用户“Do you want to use a minimal system startup instead of the SASL startup ”(你是否使用一个最小系统启动来代替SASL 启动?)时如果回答yes (是),start_clean 将被用默认的引导脚本,否则默认使用start_sasl 。一份名称为“start.boot ”的以上所选择的脚本的拷贝被放到ROOT/bin 目录下。
1.3.2 用户定制的引导脚本
创建一个用户定制的引导脚本有时是很有用的或者是必需的。当运行时Erlang 系统是嵌入式模式,这尤其正确。参见代码加载策略 。
手工编写一个引导脚本是可能的,然而推荐的方式是使用函数systools:make_script/1,2 从一个发布资源文件Name.rel 来创建,这需要源代码是符合OTP 设计原则结构的应用程序(该程序并非必需在带有OTP 应用程序的终端内启动,在纯(plain )Erlang 中也可以)。
在《OTP 设计原则》和erl(4) 可以读到更多关于.rel 文件的内容。
二进制格式的引导脚本文件Name.boot 是从引导脚本文件Name.script 通过使用函数systools:script2boot(File) 来生成的。
1.4 代码加载策略
运行时系统既可以在嵌入模式 (embedded ) 也可以在交互 模式 (interactive ) 启动。这取决于命令行参数-mode 。
% erl -mode embedded
默认的模式是交互模式。
最初,代码路径包含当前的工作目录和所有位于ROOT/lib 下面的库的代码目录,ROOT 是Erlang/OTP 的安装目录。代码目录可能命名为Name[-Vsn] ,在那些具有相同的名称的中,代码服务器缺省选择版本最高的那个,后缀-Vsn 是可选的。如果在名称为Name[-Vsn] 的目录下有一个名字为ebin 的子目录,那么它就是那个被加到代码路径中的目录。
代码路径可以被扩充,使用命令行参数 -pa Directories 和 -pz Directories 。这些目录将分别被加到代码路径的开头或者结尾。例如:
% erl -pa /home/arne/mycode
代码服务器模块包含一些函数用来修改和检查代码搜索路径,参见code(3) 。
1.5 文件类型
下列文件类型在Erlang/OTP 中被定义:
文件类型 |
文件名/ 扩展名 |
相关 文档 |
模块 |
.erl |
《Erlang 参考手册》 |
包含文件 |
.hrl |
《Erlang 参考手册》 |
发布资源文件 |
.rel |
rel(4) |
应用程序资源文件 |
.app |
app(4) |
引导脚本 |
.script |
script(4) |
二进制引导脚本 |
.boot |
- |
配置文件 |
.config |
config(4) |
应用程序升级文件 |
.appup |
appup(4) |
发布升级文件 |
.relup |
relup(4) |
Copyright © 1991-2009 Ericsson AB
2 错误日志
2.1 来自运行时系统的错误信息
来自运行时系统的错误信息,通常是关于一个进程由于发生一个未被捕获的异常而终止的信息,在缺省情况下,将被写到终端(TTY ):
=ERROR REPORT==== 9-Dec-2003::13:25:02 ===
Error in process <0.27.0> with exit value: {{badmatch,[1,2,3]},[{m,f,1},{shell,eval_loop,2}]}
一个系统进程被注册为错误日志记录者 ,错误信息将被错误日志记录者所接收和记录。该进程接收所有的错误消息,这些消息来自Erlang 运行时系统,也可以来自不同于Erlang/OTP 应用程序的那些标准行为。
运行时系统所使用的退出原因(例如上面的badarg ),在《Erlang 参考手册》中“错误和错误捕获”中详细描述。
进程错误日志记录者和它的用户接口(具有相同的名字),在error loger(3) 中描述。系统允许被配置成让错误信息写入文件或者(同时)从终端(TTY )输出。而且,应用程序也可以发送一个格式化的错误信息到错误日志记录者。
2.2 SASL 错误日志
标准的行为(supervisor, gen_server, 等)发送进度和错误信息到日志。如果SASL 应用程序被启动,这些信息也将被写到终端。更多的信息请参见《SASL 用户手册》中的SASL 错误日志。
% erl -boot start_sasl
Erlang (BEAM) emulator version 5.4.13 [hipe] [threads:0] [kernel-poll]
=PROGRESS REPORT==== 31-Mar-2006::12:45:58 ===
supervisor: {local,sasl_safe_sup}
started: [{pid,<0.33.0>},
{name,alarm_handler},
{mfa,{alarm_handler,start_link,[]}},
{restart_type,permanent},
{shutdown,2000},
{child_type,worker}]
=PROGRESS REPORT==== 31-Mar-2006::12:45:58 ===
supervisor: {local,sasl_safe_sup}
started: [{pid,<0.34.0>},
{name,overload},
{mfa,{overload,start_link,[]}},
{restart_type,permanent},
{shutdown,2000},
{child_type,worker}]
=PROGRESS REPORT==== 31-Mar-2006::12:45:58 ===
supervisor: {local,sasl_sup}
started: [{pid,<0.32.0>},
{name,sasl_safe_sup},
{mfa,{supervisor,
start_link,
[{local,sasl_safe_sup},sasl,safe]}},
{restart_type,permanent},
{shutdown,infinity},
{child_type,supervisor}]
=PROGRESS REPORT==== 31-Mar-2006::12:45:58 ===
supervisor: {local,sasl_sup}
started: [{pid,<0.35.0>},
{name,release_handler},
{mfa,{release_handler,start_link,[]}},
{restart_type,permanent},
{shutdown,2000},
{child_type,worker}]
=PROGRESS REPORT==== 31-Mar-2006::12:45:58 ===
application: sasl
started_at: nonode@nohost
Eshell V5.4.13 (abort with ^G)
1>
Copyright © 1991-2009 Ericsson AB
3 创建第一个目标系统
3.1 简介
使用Erlang/OTP 来创建一个系统,最简单的方式是在那里安装Erlang/OTP ,另外安装所需要的应用程序代码,然后启动此Erlang 运行时系统,确认代码路径包含此应用程序的代码位置。
通常,这并不是一个另人满意的应用Erlang/OTP 应用系统的方式。一个开发者可能为了一个特殊的目的,创建一个新的Erlang/OTP 兼容应用程序,一些最初的Erlang/OTP 应用程序可能是为了解决一些完全不相干的问题。因此,必需要能够创建基于指定的Erlang/OTP 系统的新系统,在这里,那些不必要的应用程序将被删除,而且包含一组新的应用程序在此新系统中。文档和源代码是不必要的,当然也不会被包含。
本章是关于创建以上这样的一个系统,我们称之为目标系统 。
在下面的一些小节,我们将考虑为不同的需求和功能来创建目标系统。
我们只考虑运行在UINX 系统下的Erlang/OTP 系统的情况。
这里有一个示例的Erlang 模块target_system.erl ,它包含一些用来创建和安装目标系统的函数。此模块在下面的例子中被使用。此模块的源代码清单在本章的最后给出。
3.2 创建一个目标系统
这里假定你已经有一个可以工作的符合OTP 设计原则结构Erlang/OTP 系统。
步骤1. 首先创建一个.rel 文件(参见rel(4) )来指出这个新的基本目标系统的版本和列出所有的需要包含的应用程序。
%% mysystem.rel
{release,
{"MYSYSTEM", "FIRST"},
{erts, "5.1"},
[{kernel, "2.7"},
{stdlib, "1.10"},
{sasl, "1.9.3"},
{pea, "1.0"}]}.
这里列出的应用程序不仅仅包括原始的Erlang/OTP 应用程序,而且也可以包含你自己写的新的应用程序(例如上面例子中的应用程序pea )。
步骤2 . 从mysystem.rel 文件所在的目录启动Erlang/OTP 系统:
os> erl -pa /home/user/target_system/myapps/pea-1.0/ebin
在这里,pea-1.0 的ebin 目录也是要提供的。
步骤3. 现在,创建目标系统:
1> target_system:create("mysystem").
函数target_system:create/1 执行以下步骤:
3. 通过调用函数systools:make_tar/2 来创建文件mysystem.tar.gz 。该文件包含下列内容:
erts-5.1/bin/
releases/FIRST/start.boot
releases/mysystem.rel
lib/kernel-2.7/
lib/stdlib-1.10/
lib/sasl-1.9.3/
lib/pea-1.0/
文件releases/FIRST/start.boot 是mysystem.boot 的拷贝,同时的mysystem.rel 被复制到release 目录。
4. 创建临时目录tmp ,将文件mysystem,tar.gz 解压到该目录。
9. 创建文件tmp/releases/start_erl.data ,包含内容“5.1 FIRST ”。
10. 从目录tmp 重新创建文件mysystem.tar.gz ,删除tmp 。
3.3 安装目标系统
步骤4. 安装创建好的目标系统到一个合适的目录下。
2> target_system:install("mysystem", "/usr/local/erl-target").
函数target_system/2 执行下列步骤:
3.4 启动目标系统
现在我们有一个可以使用各种不同方式启动的目标系统了。
我们以基础目标系统 的方式来启动它:
os> /usr/local/erl-target/bin/erl
这里只有kernel 和stdlib 应用程序被启动,换言之,它就象一个普通的开发系统那样启动。这里只有两个文件是必需的:bin/erl (来自erts-5.1/bin/erl.src )和bin/start.boot (plain.boot 文件的拷贝)。
我们也可以启动一个分布式的系统(需要bin/epmd )。
要启动在原来的文件mysystem.rel 内指定的所有应用程序,如下所示使用 -boot 参数:
os> /usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FIRST/start
我们象上面这样来启动一个简单目标系统 。唯一不同的是现在文件release/RELEASES 是为代码在运行时正确进行热替换而准备。
为了启动一个嵌入式目标系统 ,需要使用shell 脚本bin/start 。该shell 脚本调用bin/run_erl ,在这个脚本里又调用bin/start_erl (概括地说,start_erl 是一个erl 的嵌入式变体)。
这个shell 脚本仅仅是一个样例。你应该编辑它来符合你的需要。典型的用法是当UNIX 系统启动的时候执行它。
run_erl 是一个包装,它提供将运行时系统的输出记录到日志文件内。它同时也提供一个附加到Erlang Shell 的简单机制(to_erl )。
start_erl 需要提供根目录(“/usr/local/erl-target ”),发布目录(“/usr/local/erl-target/releases ”)和文件start_erl.data 的所在目录。它从start_erl.data 文件中读取运行时系统的版本(“5.1 ”)和发布版本(“FIRST ”),启动相应版本的运行时系统,并提供参数-boot来指定相应发布版本的引导文件(“releases/FIRST/start.boot ”)。
start_erl 同时也假定在发布版本目录(“releases/FIRST/sys.config ”)下有一个名称为sys.config 的文件。这将是下一节的主题。
shell 脚本start_erl 应该能在无需用户交互地正常工作。
3.5 系统配置参数
上节所提到的start_erl 需要在发布版本目录下的一个文件sys.config (“releases/FIRST/sys.config ”)。如果不存在这样一个文件,系统将启动失败。因此,这个文件也需要添加到正确的位置。
如果你有一些系统配置数据既和文件位置无关也与地点(site )无关,那么也许可以很方便地尽早去创建sys.config ,这样,它就被target_system:create/1 创建成为目标系统tar 文件的一部分。实际上,如果你创建时,在当前目录不仅有mysystem.rel 而且还包括文件sys.config ,那么该文件自然而然地就被默默放到了恰当的目录下。
3.6 与安装脚本的差异
上面描述的install/2 过程在某些方面与真正的安装shell 脚本稍有不同。实际上,函数create/1 尽可能地制作出一个完整的发布包,而让install/2 过程仅仅需要考虑那些与安装位置相关的文件。
3.7 target_system.erl 源代码清单
-module (target_system).
-include_lib ("kernel/include/file.hrl" ).
-export ([create/1, install/2]).
-define (BUFSIZE, 8192).
%% Note: RelFileName below is the *stem* without trailing .rel,
%% .script etc.
%%
%% create(RelFileName)
%%
create (RelFileName) ->
RelFile = RelFileName ++ ".rel",
io:fwrite("Reading file: /"~s/" ...~n", [RelFile]),
{ok, [RelSpec]} = file:consult(RelFile),
io:fwrite("Creating file: /"~s/" from /"~s/" ...~n",
["plain.rel", RelFile]),
{release,
{RelName, RelVsn},
{erts, ErtsVsn},
AppVsns} = RelSpec,
PlainRelSpec = {release,
{RelName, RelVsn},
{erts, ErtsVsn},
lists:filter(fun({kernel, _}) ->
true;
({stdlib, _}) ->
true;
(_) ->
false
end, AppVsns)
},
{ok, Fd} = file:open("plain.rel", [write]),
io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
file:close(Fd),
io:fwrite("Making /"plain.script/" and /"plain.boot/" files ...~n"),
make_script("plain"),
io:fwrite("Making /"~s.script/" and /"~s.boot/" files ...~n",
[RelFileName, RelFileName]),
make_script(RelFileName),
TarFileName = io_lib:fwrite("~s.tar.gz", [RelFileName]),
io:fwrite("Creating tar file /"~s/" ...~n", [TarFileName]),
make_tar(RelFileName),
io:fwrite("Creating directory /"tmp/" ...~n"),
file:make_dir("tmp"),
io:fwrite("Extracting /"~s/" into directory /"tmp/" ...~n", [TarFileName]),
extract_tar(TarFileName, "tmp"),
TmpBinDir = filename:join(["tmp", "bin"]),
ErtsBinDir = filename:join(["tmp", "erts-" ++ ErtsVsn, "bin"]),
io:fwrite("Deleting /"erl/" and /"start/" in directory /"~s/" ...~n",
[ErtsBinDir]),
file:delete(filename:join([ErtsBinDir, "erl"])),
file:delete(filename:join([ErtsBinDir, "start"])),
io:fwrite("Creating temporary directory /"~s/" ...~n", [TmpBinDir]),
file:make_dir(TmpBinDir),
io:fwrite("Copying file /"plain.boot/" to /"~s/" ...~n",
[filename:join([TmpBinDir, "start.boot"])]),
copy_file("plain.boot", filename:join([TmpBinDir, "start.boot"])),
io:fwrite("Copying files /"epmd/", /"run_erl/" and /"to_erl/" from /n"
"/"~s/" to /"~s/" ...~n",
[ErtsBinDir, TmpBinDir]),
copy_file(filename:join([ErtsBinDir, "epmd"]),
filename:join([TmpBinDir, "epmd"]), [preserve]),
copy_file(filename:join([ErtsBinDir, "run_erl"]),
filename:join([TmpBinDir, "run_erl"]), [preserve]),
copy_file(filename:join([ErtsBinDir, "to_erl"]),
filename:join([TmpBinDir, "to_erl"]), [preserve]),
StartErlDataFile = filename:join(["tmp", "releases", "start_erl.data"]),
io:fwrite("Creating /"~s/" ...~n", [StartErlDataFile]),
StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
write_file(StartErlDataFile, StartErlData),
io:fwrite("Recreating tar file /"~s/" from contents in directory "
"/"tmp/" ...~n", [TarFileName]),
{ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
{ok, Cwd} = file:get_cwd(),
file:set_cwd("tmp"),
erl_tar:add(Tar, "bin", []),
erl_tar:add(Tar, "erts-" ++ ErtsVsn, []),
erl_tar:add(Tar, "releases", []),
erl_tar:add(Tar, "lib", []),
erl_tar:close(Tar),
file:set_cwd(Cwd),
io:fwrite("Removing directory /"tmp/" ...~n"),
remove_dir_tree("tmp"),
ok.
install(RelFileName, RootDir) ->
TarFile = RelFileName ++ ".tar.gz",
io:fwrite("Extracting ~s ...~n", [TarFile]),
extract_tar(TarFile, RootDir),
StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
{ok, StartErlData} = read_txt_file(StartErlDataFile),
[ErlVsn, RelVsn| _] = string:tokens(StartErlData, " /n"),
ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
BinDir = filename:join([RootDir, "bin"]),
io:fwrite("Substituting in erl.src, start.src and start_erl.src to/n"
"form erl, start and start_erl .../n"),
subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
[{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
[preserve]),
io:fwrite("Creating the RELEASES file .../n"),
create_RELEASES(RootDir,
filename:join([RootDir, "releases", RelFileName])).
%% LOCALS
%% make_script(RelFileName)
%%
make_script(RelFileName) ->
Opts = [no_module_tests],
systools:make_script(RelFileName, Opts).
%% make_tar(RelFileName)
%%
make_tar(RelFileName) ->
RootDir = code:root_dir(),
systools:make_tar(RelFileName, [{erts, RootDir}]).
%% extract_tar(TarFile, DestDir)
%%
extract_tar(TarFile, DestDir) ->
erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
create_RELEASES(DestDir, RelFileName) ->
release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
lists:foreach(fun(Script) ->
subst_src_script(Script, SrcDir, DestDir,
Vars, Opts)
end, Scripts).
subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
subst_file(filename:join([SrcDir, Script ++ ".src"]),
filename:join([DestDir, Script]),
Vars, Opts).
subst_file(Src, Dest, Vars, Opts) ->
{ok, Conts} = read_txt_file(Src),
NConts = subst(Conts, Vars),
write_file(Dest, NConts),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
file:write_file_info(Dest, FileInfo);
false ->
ok
end.
%% subst(Str, Vars)
%% Vars = [{Var, Val}]
%% Var = Val = string()
%% Substitute all occurrences of %Var% for Val in Str, using the list
%% of variables in Vars.
%%
subst(Str, Vars) ->
subst(Str, Vars, []).
subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when C == $_ ->
subst_var([C| Rest], Vars, Result, []);
subst([C| Rest], Vars, Result) ->
subst(Rest, Vars, [C| Result]);
subst([], _Vars, Result) ->
lists:reverse(Result).
subst_var([$%| Rest], Vars, Result, VarAcc) ->
Key = lists:reverse(VarAcc),
case lists:keysearch(Key, 1, Vars) of
{value, {Key, Value}} ->
subst(Rest, Vars, lists:reverse(Value, Result));
false ->
subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
end;
subst_var([C| Rest], Vars, Result, VarAcc) ->
subst_var(Rest, Vars, Result, [C| VarAcc]);
subst_var([], Vars, Result, VarAcc) ->
subst([], Vars, [VarAcc ++ [$%| Result]]).
copy_file(Src, Dest) ->
copy_file(Src, Dest, []).
copy_file(Src, Dest, Opts) ->
{ok, InFd} = file:open(Src, [raw, binary, read]),
{ok, OutFd} = file:open(Dest, [raw, binary, write]),
do_copy_file(InFd, OutFd),
file:close(InFd),
file:close(OutFd),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
file:write_file_info(Dest, FileInfo);
false ->
ok
end.
do_copy_file(InFd, OutFd) ->
case file:read(InFd, ?BUFSIZE) of
{ok, Bin} ->
file:write(OutFd, Bin),
do_copy_file(InFd, OutFd);
eof ->
ok
end.
write_file(FName, Conts) ->
{ok, Fd} = file:open(FName, [write]),
file:write(Fd, Conts),
file:close(Fd).
read_txt_file(File) ->
{ok, Bin} = file:read_file(File),
{ok, binary_to_list(Bin)}.
remove_dir_tree(Dir) ->
remove_all_files(".", [Dir]).
remove_all_files(Dir, Files) ->
lists:foreach(fun(File) ->
FilePath = filename:join([Dir, File]),
{ok, FileInfo} = file:read_file_info(FilePath),
case FileInfo#file_info.type of
directory ->
{ok, DirFiles} = file:list_dir(FilePath),
remove_all_files(FilePath, DirFiles),
file:del_dir(FilePath);
_ ->
file:delete(FilePath)
end
end, Files).