erlang 启动顺序
本文解读erlang的启动顺序,VM在启动的时候到底做了什么?
1.启动入口
调用堆栈如下:
init:boot/1(Pid<0,0,0>)
otp_ring0:start/2(系统入口)
2.使用-init_debug
可以将boot script
信息打印出来
$ erl -init_debug
{progress,preloaded}
{progress,kernel_load_completed}
{progress,modules_loaded}
{start,heart}
{start,logger}
{start,application_controller}
{progress,init_kernel_started}
{apply,......}
{progress,applications_loaded}
{apply,{application,start_boot,[kernel,permanent]}}
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
{apply,{application,start_boot,[stdlib,permanent]}}
{apply,{c,erlangrc,[]}}
{progress,started}
Eshell V10.3.4 (abort with ^G)
1>
3.代码解读(init.erl
)
% init.erl
boot(BootArgs)->
...
...
do_boot(Init, Flags, Start)->
...
% 获取 -boot 参数,默认 "/bin/start"
BootFile = bootfile(Flags,Root),
% 读取 BootFile.boot 文件,文件格式是 {script,Id,CmdList}
% Id: Otp版本,我本机是"21”
% CmdList: 启动参数
% {preLoaded...}
% {progress...}
% {path...}
% {kernelProcess....}
% {apply,{M,F,A}},
BootList = get_boot(BootFile,Root),
......
eval_script(BootList, Es)
4.$ROOT/bin/start.boot
脚本内容
1> {ok,[Root]}=init:get_argument(root).
{ok,[["/usr/lib/erlang"]]}
2> BootFile = lists:flatten(Root ++ "/bin/start.boot").
"/usr/lib/erlang/bin/start.boot"
3> {ok,Bin}=file:read_file(BootFile).
{ok,<<131,104,3,100,0,6,115,99,114,105,112,116,104,2,107,
0,10,69,114,108,97,110,103,47,79,84,80,...>>}
4> Term = erlang:binary_to_term(Bin).
{script,{"Erlang/OTP","21"},
[{preLoaded,[atomics,counters,erl_prim_loader,erl_tracer...]},
{kernel_load_completed},
{progress,kernel_load_completed},
{path,["$ROOT/lib/kernel-6.3.1/ebin"]},
{primLoad,[error_handler,application...]},
{path,["$ROOT/lib/stdlib-3.8.1/ebin"]},
{primLoad,...},
% kernel,stdlib模块载入完成
{progress,modules_loaded},
{path,["$ROOT/lib/kernel-6.3.1/ebin",
"$ROOT/lib/stdlib-3.8.1/ebin"]},
% 开启 heart
{kernelProcess,heart,{heart,start,[]}},
% 开启 logger_server
{kernelProcess,logger,{logger_server,start_link,[]}},
% 开启 application_controller
{kernelProcess,application_controller...},
{progress,init_kernel_started},
% 载入 application: stdlib,kernel
{apply,{application,load,[{application,stdlib,[...]}]}},
{progress,applications_loaded},
% 启动 application kernel
{apply,{application,start_boot,[kernel,permanent]}},
% 启动 application stdlib
{apply,{application,start_boot,[stdlib|...]}},
% 运行 $HOME/.erlang文件,这边可以参考我的另外一篇文章erlang中的特殊文件
{apply,{c,erlangrc,[]}},
{progress,started}]}
5>
从上文可以看到模块的加载顺序是
- erts 中的模块,如atomics,counters...
- stdlib 中的模块,如error_handler,gen_server...
- kernel 中的模块,如application_starter...
- 其他自定义的application中的模块,由于模块不像其他语言有命名空间,所以自己起模块名得注意了
5. erlang 是如何加载系统
erlang按照boot文件中的cmd list顺序执行
eval_script([{progress, Info} = Progress | T], #es{debug = Deb} = Es) ->
% progress 只是打印进度,别的什么都不干
debug(Deb, Progress),
init ! {self(), progress, Info},
eval_script(T, Es);
eval_script([{preLoaded, _} | T], #es{} = Es) ->
% preLoaded 什么都不干
eval_script(T, Es);
eval_script([{path, Path} | T], #es{path = false, pa = Pa, pz = Pz,
% 添加PATH
path_choice = PathChoice,
vars = Vars} = Es) ->
RealPath0 = make_path(Pa, Pz, Path, Vars),
RealPath = patch_path(RealPath0, PathChoice),
erl_prim_loader:set_path(RealPath),
eval_script(T, Es);
eval_script([{path, _} | T], #es{} = Es) ->
%% Ignore, use the command line -path flag.
eval_script(T, Es);
eval_script([{kernel_load_completed} | T], #es{load_mode = Mode} = Es0) ->
Es = case Mode of
embedded -> Es0;
_ -> Es0#es{prim_load = false}
end,
eval_script(T, Es);
eval_script([{primLoad, Mods} | T],
#es{init = Init, prim_load = PrimLoad} = Es) when is_list(Mods) ->
% primLoad 载入需要的模块
case PrimLoad of
true ->
load_modules(Mods, Init);
false ->
%% Do not load now, code_server does that dynamically!
ok
end,
eval_script(T, Es);
eval_script([{kernelProcess, Server, {Mod, Fun, Args}} | T],
#es{init = Init, debug = Deb} = Es) ->
% kernelProcess 开启服务,如heart,logger, application_controller
debug(Deb, {start, Server}),
start_in_kernel(Server, Mod, Fun, Args, Init),
eval_script(T, Es);
eval_script([{apply, {Mod, Fun, Args}} = Apply | T], #es{debug = Deb} = Es) ->
% 执行脚本MFA,如启动 kernel,stdlib,c:erlnagc()
debug(Deb, Apply),
apply(Mod, Fun, Args),
eval_script(T, Es);
eval_script([], #es{}) ->
ok;
eval_script(What, #es{}) ->
% 不能识别的CMD,报错直接退出
exit({'unexpected command in bootfile', What}).
总结
了解并掌握erlang的启动顺序,加深对VM的了解,并且对于自己实现boot file有很大的帮助。
网友评论