erlang的启动引导部分代码,本文章讲解的是排除C源码,从erlang代码开始的部分,C代码部分以后补充,
otp_ring0:start/2为第一个函数调用,源码内容很简单,我就不贴出来了,就是一个调用init:boot/0的功能。
接下来,我们分析init:boot/0的流程
init() ->
register(init, self()),%将本进程注册为init进程
process_flag(trap_exit, true),
start_on_load_handler_process(), %spawn一个?ON_LOAD_HANDER进程,但并没有link
{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).
这段代码主要有两个功能:
1.在start_on_load_handler_process函数中,启动一个注册名为init__boot__on_load_handler的进程,这个进程从代码上看是负责载入相关模块的一些操作,但我看了一下只有在启动参数-mode minimal的时候,kernel才会往该进程发送消息,起到作用,这点我有些不是很明白,但这些与正常boot过程影响不大,暂且搁下,以后再详细分析。
2.解析命令行参数(参数分析这段请查看我的日志http://www.cnblogs.com/star-star/archive/2012/12/26/2834194.html)
分割完参数后,进入boot/3,我们查看一下以下代码
boot(Start,Flags,Args) ->
%进入do_boot函数中会spawn_link一个启动脚本解析执行进程 BootPid = do_boot(Flags,Start),
%准备boot_loop时的上下文环境 State = #state{flags = Flags, args = Args, start = Start, bootpid = BootPid},
%进入init进程主循环 boot_loop(BootPid,State).
%接下来我们查看一下do_boot函数中的代码
do_boot(Flags,Start) -> Self = self(), spawn_link(fun() -> do_boot(Self,Flags,Start) end). 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)), %根据启动参数的-init_debug项来确定是否打印出过程信息 catch ?ON_LOAD_HANDLER ! {init_debug_flag,Deb}, 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). %这里面就是执行启动参数指定启动的应用啊,执行的脚本神马的
do_boot函数的作用其实很简单,启动一个新进程,这个进程负责,加载启动脚本,在此过程中,不断像init主进程和开始创建的init__boot__on_load_handler,发送message通知,
根据启动脚本来解析执行,(这段请参考我的这篇日志http://www.cnblogs.com/star-star/archive/2012/12/27/2836074.html),
完毕后,执行start_em, 该函数是根据启动参数中的-run -s -eval等参数执行相应代码。
接下来我们看一下init主循环, boot_loop的代码。
进入boot_loop主循环,关于init进程我要说明一点,其实他有两个状态,一个循环在boot_loop中,另一个,以上文中所说的do_boot函数中启动boot脚本执行进程的正常终止为条件,
转换到loop函数中,也就是从引导状态,转为正常程序运行状态。
接下来我们来看一下init主循环代码。
boot_loop(BootPid, State) ->
receive
{BootPid,loaded,ModLoaded} ->
Loaded = State#state.loaded,
boot_loop(BootPid,State#state{loaded = [ModLoaded|Loaded]});
{BootPid,started,KernelPid} ->
boot_loop(BootPid, new_kernelpid(KernelPid, BootPid, State));
{BootPid,progress,started} ->
{InS,_} = State#state.status,
notify(State#state.subscribed),
boot_loop(BootPid,State#state{status = {InS,started},
subscribed = []});
{BootPid,progress,NewStatus} ->
{InS,_} = State#state.status,
boot_loop(BootPid,State#state{status = {InS,NewStatus}});
{BootPid,{script_id,Id}} ->
boot_loop(BootPid,State#state{script_id = Id});
%%以上的message都是在do_boot按启动脚本执行时,发送的通知消息
{'EXIT',BootPid,normal} ->
{_,PS} = State#state.status,
notify(State#state.subscribed),
%这里其实就是主循环的状态迁移了
loop(State#state{status = {started,PS},
subscribed = []});
{'EXIT',BootPid,Reason} ->
%do_boot函数进程不正常终止时
erlang:display({"init terminating in do_boot",Reason}),
crash("init terminating in do_boot", [Reason]);
{'EXIT',Pid,Reason} ->
%kernel进程不正常终止时
Kernel = State#state.kernel,
terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()!
boot_loop(BootPid,State);
{stop,Reason} ->
%init:stop 和init:reboot会发送stop消息
stop(Reason,State);
{From,fetch_loaded} -> %% Fetch and reset initially loaded modules.
From ! {init,State#state.loaded},
garb_boot_loop(BootPid,State#state{loaded = []});
{From,{ensure_loaded,Module}} ->
{Res, Loaded} = ensure_loaded(Module, State#state.loaded),
From ! {init,Res},
boot_loop(BootPid,State#state{loaded = Loaded});
Msg ->
boot_loop(BootPid,handle_msg(Msg,State))
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%以下是状态转换后的loop循环
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
loop(State) ->
receive
{'EXIT',Pid,Reason} ->
Kernel = State#state.kernel,
terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()!
loop(State);
{stop,Reason} ->
stop(Reason,State);
{From,fetch_loaded} -> %% The Loaded info is cleared in
Loaded = State#state.loaded, %% boot_loop but is handled here
From ! {init,Loaded}, %% anyway.
loop(State);
{From, {ensure_loaded, _}} ->
From ! {init, not_allowed},
loop(State);
Msg ->
%handle_msg主要处理的就是init模块所提供的一些获取arguments,status之类环境上下文的接口API了
loop(handle_msg(Msg,State))
end.
handle_msg(Msg,State0) ->
case catch do_handle_msg(Msg,State0) of
{new_state,State} -> State;
_ -> State0
end.
do_handle_msg(Msg,State) ->
#state{flags = Flags,
status = Status,
script_id = Sid,
args = Args,
subscribed = Subscribed} = State,
case Msg of
{From,get_plain_arguments} ->
From ! {init,Args};
{From,get_arguments} ->
From ! {init,get_arguments(Flags)};
{From,{get_argument,Arg}} ->
From ! {init,get_argument(Arg,Flags)};
{From,get_status} ->
From ! {init,Status};
{From,script_id} ->
From ! {init,Sid};
{From,{make_permanent,Boot,Config}} ->
{Res,State1} = make_permanent(Boot,Config,Flags,State),
From ! {init,Res},
{new_state,State1};
{From,{notify_when_started,Pid}} ->
case Status of
{InS,PS} when InS =:= started ; PS =:= started ->
From ! {init,started};
_ ->
From ! {init,ok},
{new_state,State#state{subscribed = [Pid|Subscribed]}}
end;
X ->
case whereis(user) of
undefined ->
catch error_logger ! {info, self(), {self(), X, []}};
User ->
User ! X,
ok
end
end.
以上其实基本已经把boot的init模块部分大略说了一篇,有一些细节还不是很清晰,以后补充
本文深入剖析Erlang启动引导过程,从OTP Ring0:start/2开始,介绍init:boot/0流程,包括进程注册、参数解析、脚本加载等关键步骤。

被折叠的 条评论
为什么被折叠?



