erlang init boot 浅析
原创文章,转载请注明: 转载自庆亮的博客-webgame架构
本文链接地址: erlang init boot 浅析
说明:遇到一个OTP的问题,疑惑了很久,也就有了这篇文章。
目的:跟踪Erlang的启动过程
参考这里 http://erlangdisplay.javaeye.com/blog/315497 ,直接从erts-5.7.5/src/init.erl 的boot/1函数开始(关于这个链接给出的结论以后会继续研究分析):
-spec boot([binary()]) -> no_return().
boot(BootArgs) ->
register(init, self()),
process_flag(trap_exit, true),
start_on_load_handler_process(),
{Start0,Flags,Args} = parse_boot_args(BootArgs),
Start = map(fun prepare_run_args/1, Start0),
Flags0 = flags_to_atoms_again(Flags),
boot(Start,Flags0,Args).
do_boot(Init,Flags,Start) ->
process_flag(trap_exit,true),
{Pgm0,Nodes,Id,Path} = prim_load_flags(Flags),
Root = b2s(get_flag('-root',Flags)),
PathFls = path_flags(Flags),
Pgm = b2s(Pgm0),
_Pid = start_prim_loader(Init,b2a(Id),Pgm,bs2as(Nodes),
bs2ss(Path),PathFls),
BootFile = bootfile(Flags,Root),
BootList = get_boot(BootFile,Root),
LoadMode = b2a(get_flag('-mode',Flags,false)),
Deb = b2a(get_flag('-init_debug',Flags,false)),
BootVars = get_flag_args('-boot_var',Flags),
ParallelLoad =
(Pgm =:= "efile") and (erlang:system_info(thread_pool_size) > 0),
PathChoice = code_path_choice(),
eval_script(BootList,Init,PathFls,{Root,BootVars},Path,
{true,LoadMode,ParallelLoad},Deb,PathChoice),
%% To help identifying Purify windows that pop up,
%% print the node name into the Purify log.
(catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})),
start_em(Start).
boot/3的基本流程为 start_prim_loader/6 → eval_script/8 → start_em/1
1. start_prim_loader/6启动了erl_prim_loader模块,负责代码加载(远程或者本地等)
2. eval_script/8 负责启动一些关键的进程
来看个eval_script/8参数的例子:
{[{preLoaded,[erl_prim_loader,erlang,init,otp_ring0,
prim_file,prim_inet,prim_zip,zlib]},
{progress,preloaded},
{path,["$ROOT/lib/kernel-2.13.5/ebin",
"$ROOT/lib/stdlib-1.16.5/ebin"]},
{primLoad,[error_handler]},
{kernel_load_completed},
{progress,kernel_load_completed},
{path,["$ROOT/lib/kernel-2.13.5/ebin"]},
{primLoad,[application,application_controller,
application_master,application_starter,auth,code,
code_server,disk_log,disk_log_1,disk_log_server,
disk_log_sup,dist_ac,dist_util,erl_boot_server,erl_ddll,
erl_distribution,erl_epmd,erl_reply|...]},
{path,["$ROOT/lib/stdlib-1.16.5/ebin"]},
{primLoad,[array,base64,beam_lib,c,calendar,dets,
dets_server,dets_sup,dets_utils,dets_v8,dets_v9,dict,
digraph,digraph_utils,edlin,edlin_expand|...]},
{progress,modules_loaded},
{path,["$ROOT/lib/kernel-2.13.5/ebin",
"$ROOT/lib/stdlib-1.16.5/ebin"]},
{kernelProcess,heart,{heart,start,[]}},
{kernelProcess,error_logger,{error_logger,start_link,[]}},
{kernelProcess,application_controller,
{application_controller,start,
[{application,kernel,
[{description,[...]},{vsn,…},{…}|…]}]}},
{progress,init_kernel_started},
{apply,{application,load,
[{application,stdlib,[{description,...},{...}|...]}]}},
{progress,applications_loaded},
{apply,{application,start_boot,[kernel,permanent]}},
{apply,{application,start_boot,[stdlib,permanent]}},
{apply,{c,erlangrc,[]}},
{progress,started}],
<0.0.0>,
{[],[]},
{"/usr/local/lib/erlang",[]},
false,
{true,false,false},
false,relaxed}
查看eval_script/8的实现(代码就不贴了),发现启动了上面参数中 kernelProcess对应键值的进程,如:
{kernelProcess,heart,{heart,start,[]}},
{kernelProcess,error_logger,{error_logger,start_link,[]}},
{kernelProcess,application_controller,
{application_controller,start,
[{application,kernel,
eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|CfgL],Init,
PathFs,Vars,P,Ph,Deb,PathChoice) ->
debug(Deb,{start,Server}),
start_in_kernel(Server,Mod,Fun,Args,Init),
eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
start_in_kernel(Server,Mod,Fun,Args,Init) ->
Res = apply(Mod,Fun,Args),
Init ! {self(),started,{Server,Res}},
receive
{Init,ok,Pid} ->
unlink(Pid), %% Just for sure…
ok;
{Init,ignore} ->
ignore
end.
最终产生一条消息给init:
Init ! {self(),started,{Server,Res}},
{BootPid,started,KernelPid} ->
boot_loop(BootPid, new_kernelpid(KernelPid, BootPid, State));
init收到消息后,就将该进程加入到kernel pids的队列中了。
new_kernelpid({Name,{ok,Pid}},BootPid,State) when is_pid(Pid) ->
link(Pid),
BootPid ! {self(),ok,Pid},
Kernel = State#state.kernel,
State#state{kernel = [{Name,Pid}|Kernel]};
new_kernelpid({_Name,ignore},BootPid,State) ->
BootPid ! {self(),ignore},
State;
new_kernelpid({Name,What},BootPid,State) ->
erlang:display({"could not start kernel pid",Name,What}),
clear_system(BootPid,State),
crash("could not start kernel pid", [Name, What]).
{kernelProcess,application_controller,
{application_controller,start,
[{application,kernel,
application_controller启动后会启动kernel app,至此erlang的启动过程就大致的有个了解了,而关于application的启动过程就不在本次分析范围之内了。
结论:
系统从init<0.0.0>开始,一些关键的进程如:error_logger、application_controller、erl_prim_load等都会预先启动并加入到kernel pids中。
附言:
本次跟踪自然又离不开余锋老大(http://blog.yufeng.info/ )的指点,init.erl是preloaded模块:
{preLoaded,[erl_prim_loader,erlang,init,otp_ring0,
prim_file,prim_inet,prim_zip,zlib]},
而由于本文之前的无知,导致我去尝试修改init.erl。 如果大家需要跟踪init.erl的启动细节,需要修改源码包中的 erts/preloaded/src/init.erl、make && make install,由于此时系统还没跑起来,所以error_logger io之类的模块是没有用的,幸运的是preloaded里面有 prim_file,可以通过 prim_file:write_file/2把一些状态写入到文件中以便查看。

