Erlang虚拟机的启动

时间:2022-06-01 16:58:44

Erlang虚拟机的启动

erl实际上是一个shell脚本,设置几个环境变量之后,调用执行erlexec。erlexec的入口点在 otp_src_R15B01/erts/etc/common/erlexec.c 文件。erlexec的main函数首先分析erl传入的参数和环境变量,选择正确版本的beam可执行文件,然后将传入的参数整理好,加入一些默认参数,最后通过系统调用execv运行beam虚拟机。例如在smp环境中,运行的就是 beam.smp 版本的虚拟机。因此,erl和erlexec都是加载器,最终执行的Erlang虚拟机进程是名字为beam系列的进程。

beam进程的入口点在 otp_src_R15B01/erts/emulator/sys/unix/erl_main.c 。在这个文件中,main函数只有一行:

  1. erl_start(argc, argv);

通过erl_start这个函数真正进入Erlang虚拟机的世界了。erl_start函数位于 otp_src_R15B01/erts/emulator/beam/erl_init.c 文件,Erlang虚拟机初始化相关的代码基本上都在这个文件中。这个函数大约600行,但是结构简单,大部分代码都是在处理参数。把erl_start的主干理出来,就是这样的:

  1. void erl_start(int argc, char **argv)
  2. {
  3. early_init(&argc, argv);
  4. 处理各种参数
  5. 设置信号处理函数
  6. erl_init(ncpu);
  7. init_shared_memory(boot_argc, boot_argv);
  8. load_preloaded();
  9. erts_initialized = 1;
  10. erl_first_process_otp("otp_ring0", NULL, 0, boot_argc, boot_argv);
  11. erts_start_schedulers();
  12. erts_sys_main_thread();
  13. }

early_init()函数进行一些非常底层的初始化工作。erl_init()处理一些和Erlang虚拟机本身的初始化操作,例如各种数据结构的初始化。init_shared_memory()进行一些和内存回收相关的初始化。

load_preloaded()函数将需要预加载的Erlang模块加载至虚拟机。需要预加载的模块都在 otp_src_R15B01/erts/preloaded/ebin 目录下。由于在build Erlang/OTP的时候,本地应该还没有Erlang编译器,所以这个目录下提供的都是编译好的.beam文件。这些模块的源码位于otp_src_R15B01/erts/preloaded/src 目录。预加载模块在build的时候由工具程序 make_preload 生成C语言文件硬编码在虚拟机中了。如果想要修改预加载的文件,例如在里面加上 erlang:display() 表达式打印调试信息,可以修改src中的文件,然后通过编译器erlc生成.beam文件保存在 otp_src_R15B01/erts/preloaded/ebin目录下覆盖原来的文件,再build即可。

在预加载的文件夹中可以看到,预加载的有以下模块:

erl_prim_loader:主要加载器,负责所有模块的加载

erlang:对虚拟机提供的一些BIF的接口

init:init进程的代码

otp_ring0:Erlang虚拟机中第一个进程的代码,启动init

prim_file:文件操作接口

prim_inet:网络操作接口

prim_zip:压缩文件操作接口

zlib:zlib库

把这些必要模块都加载至虚拟机之后,通过erl_first_process_otp()函数创建了Erlang虚拟机上的第一个进程,调用 otp_ring0 模块中的start/2函数。start/2 函数运行init模块的 boot/1 函数,之后开始Erlang/OTP系统的引导过程。这里先把虚拟机的启动过程分析完再讲述Erlang/OTP的引导过程。

创建了第一个进程之后,进程还不能运行,因为还没有创建调度器。erts_start_schedulers()根据CPU的核心数和用户通过参数设置的数值启动某个数目的调度器线程。每一个调度器都在一个线程中运行。调度器挑选要执行的进程,然后执行进程,当进程的reds用完或进程等待IO挂起的时候再挑选另一个进程执行。以后再撰文详细分析Erlang调度器的工作原理。运行了erts_start_schedulers()函数之后Erlang虚拟机才真正运转起来。

启动调度器之后,调用erts_sys_main_thread()函数,也就是说beam进程的主线程进入了erts_sys_main_thread()函数。下面简单分析一下erts_sys_main_thread()函数。

  1. void erts_sys_main_thread(void)
  2. {
  3. erts_thread_disable_fpe();
  4. smp_sig_notify(0); /* Notify initialized */
  5. while (1) {
  6. /* Wait for a signal to arrive... */
  7. select(0, NULL, NULL, NULL, NULL);
  8. }
  9. }

这个函数很简单,屏蔽浮点数异常、通知信号处理线程已经完成了初始化,然后进入一个死循环等待信号。这个select调用表示永远等待文件IO操作,但是什么文件也不等,只是把线程挂起。但是这个函数在收到信号的时候会返回。这里顺便提一下Erlang虚拟机中的信号处理。在之前初始化的时候,设置了信号处理函数,也就是通过函数 init_break_handler() 设置了一些信号的处理函数。这些信号处理函数收到了信号之后实际上将信号通过管道转发给了一个专门处理信号的线程,之前在调用 early_init() 的时候创建了这个线程,这个信号处理线程运行的函数是 signal_dispatcher_thread_func(),这个函数是一个死循环,等待从管道中读取值。虚拟机的主线程通过 smp_sig_notify() 函数将通知消息写入管道发给信号处理线程。

从Erlang虚拟机处理信号的方式可以看出,这种处理方式也是Erlang提倡的进程间通信方式。

下面分析otp_ring0的start/2调用init的boot/1引导Erlang/OTP系统的过程。

init进程的引导过程

init:boot/1的代码如下:

  1. boot(BootArgs) ->
  2. register(init, self()),
  3. process_flag(trap_exit, true),
  4. start_on_load_handler_process(),
  5. {Start0,Flags,Args} = parse_boot_args(BootArgs),
  6. Start = map(fun prepare_run_args/1, Start0),
  7. Flags0 = flags_to_atoms_again(Flags),
  8. boot(Start,Flags0,Args).

第2行将当前进程注册为init,于是我们就有了init进程。第4行启动了一个新的进程ON_LOAD_HANDLER,这个进程处理一些和加载相关的事件。然后对传入的参数做一些处理,Start是erl -s参数传入的要运行的MFA列表,Flags0是调用erl传入的一些标志,Args是erl -extra 传入的一些额外参数。接下来这些参数传入boot/3。下面是boot/3的代码:

  1. boot(Start,Flags,Args) ->
  2. BootPid = do_boot(Flags,Start),
  3. State = #state{flags = Flags,
  4. args = Args,
  5. start = Start,
  6. bootpid = BootPid},
  7. boot_loop(BootPid,State).

boot/3调用do_boot/2,设置State,然后就进入boot_loop/2循环。下面是do_boot/2的代码:

  1. do_boot(Flags,Start) ->
  2. Self = self(),
  3. spawn_link(fun() -> do_boot(Self,Flags,Start) end).
  4. do_boot(Init,Flags,Start) ->
  5. process_flag(trap_exit,true),
  6. {Pgm0,Nodes,Id,Path} = prim_load_flags(Flags),
  7. Root = b2s(get_flag('-root',Flags)),
  8. PathFls = path_flags(Flags),
  9. Pgm = b2s(Pgm0),
  10. _Pid = start_prim_loader(Init,b2a(Id),Pgm,bs2as(Nodes),
  11. bs2ss(Path),PathFls),
  12. BootFile = bootfile(Flags,Root),
  13. BootList = get_boot(BootFile,Root),
  14. LoadMode = b2a(get_flag('-mode',Flags,false)),
  15. Deb = b2a(get_flag('-init_debug',Flags,false)),
  16. catch ?ON_LOAD_HANDLER ! {init_debug_flag,Deb},
  17. BootVars = get_flag_args('-boot_var',Flags),
  18. ParallelLoad =
  19. (Pgm =:= "efile") and (erlang:system_info(thread_pool_size) > 0),
  20. PathChoice = code_path_choice(),
  21. eval_script(BootList,Init,PathFls,{Root,BootVars},Path,
  22. {true,LoadMode,ParallelLoad},Deb,PathChoice),
  23. %% To help identifying Purify windows that pop up,
  24. %% print the node name into the Purify log.
  25. (catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})),
  26. start_em(Start).

do_boot/2创建了一个负责引导过程的进程(do_boot/3,没有register,让我们称为do_boot),boot/2最后进入了一个boot_loop循环,接受来自do_boot进程的消息。现在系统上有3个进程,如下图所示:

Erlang虚拟机的启动

此时的<0.0.0>在boot_loop循环等待接受<0.2.0>发出的和boot相关的消息,<0.1.0>在等待接收和加载相关的消息。下面看do_boot/3的引导过程。第7-10行从传入的参数中获得加载器相关的参数,然后在第11行调用函数start_prim_loader通过erl_prim_loader模块的start/3函数创建了加载器进程。第13-14行从启动脚本中获得启动指令列表。有关启动脚本的格式参见文档 erl -man script ,启动脚本描述了Erlang运行时系统启动的过程,包含了启动过程要执行的一系列指令。如果启动erl的时候没有带-boot Name参数,那么默认使用start.boot启动脚本。start.boot是由start.script生成的。start.script内容摘要如下所示:

  1. {script,
  2. {"OTP  APN 181 01","R15B01"},
  3. [{preLoaded,
  4. [erl_prim_loader,erlang,init,otp_ring0,prim_file,prim_inet,prim_zip,zlib]},
  5. {progress,preloaded},
  6. {path,["$ROOT/lib/kernel/ebin","$ROOT/lib/stdlib/ebin"]},
  7. {primLoad,[error_handler]},
  8. {kernel_load_completed},
  9. {progress,kernel_load_completed},
  10. {path,["$ROOT/lib/kernel/ebin"]},
  11. {primLoad,
  12. [application,application_controller,application_master,
  13. ...
  14. standard_error,user,user_drv,user_sup,wrap_log_reader]},
  15. {path,["$ROOT/lib/stdlib/ebin"]},
  16. {primLoad,
  17. [array,base64,beam_lib,binary,c,calendar,dets,dets_server,dets_sup,
  18. ...
  19. supervisor_bridge,sys,timer,unicode,win32reg,zip]},
  20. {progress,modules_loaded},
  21. {path,["$ROOT/lib/kernel/ebin","$ROOT/lib/stdlib/ebin"]},
  22. {kernelProcess,heart,{heart,start,[]}},
  23. {kernelProcess,error_logger,{error_logger,start_link,[]}},
  24. {kernelProcess,application_controller,
  25. {application_controller,start,
  26. [{application,kernel,
  27. ...}]}},
  28. {progress,init_kernel_started},
  29. {apply,
  30. {application,load,
  31. [{application,stdlib,
  32. ...}]}},
  33. {progress,applications_loaded},
  34. {apply,{application,start_boot,[kernel,permanent]}},
  35. {apply,{application,start_boot,[stdlib,permanent]}},
  36. {apply,{c,erlangrc,[]}},
  37. {progress,started}]}.

do_boot/3中的BootFile就是这个文件,BootList就是从第3行开始的这个列表。列表中的每一项表示一个动作,这些动作包括preLoaded、progress、path、primLoad、kernelProcess和apply,这些动作在erl -man script文档中有详细的解释。do_boot/3的第23行调用eval_script/8函数负责执行这个列表中的每一个动作。下面是eval_script/8的代码节选:

  1. eval_script([{progress,Info}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) ->
  2. debug(Deb,{progress,Info}),
  3. init ! {self(),progress,Info},
  4. eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
  5. eval_script([{preLoaded,_}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) ->
  6. eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
  7. eval_script([{path,Path}|CfgL],Init,{Pa,Pz},Vars,false,Ph,Deb,PathChoice) ->
  8. ...
  9. eval_script(CfgL,Init,{Pa,Pz},Vars,false,Ph,Deb,PathChoice);
  10. eval_script([{path,_}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) ->
  11. eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
  12. eval_script([{kernel_load_completed}|CfgL],Init,PathFs,Vars,P,{_,embedded,Par},Deb,PathChoice) ->
  13. eval_script(CfgL,Init,PathFs,Vars,P,{true,embedded,Par},Deb,PathChoice);
  14. eval_script([{kernel_load_completed}|CfgL],Init,PathFs,Vars,P,{_,E,Par},Deb,PathChoice) ->
  15. eval_script(CfgL,Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice);
  16. eval_script([{primLoad,Mods}|CfgL],Init,PathFs,Vars,P,{true,E,Par},Deb,PathChoice)
  17. ...
  18. eval_script(CfgL,Init,PathFs,Vars,P,{true,E,Par},Deb,PathChoice);
  19. eval_script([{primLoad,_Mods}|CfgL],Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice) ->
  20. eval_script(CfgL,Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice);
  21. eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|CfgL],Init,
  22. PathFs,Vars,P,Ph,Deb,PathChoice) ->
  23. start_in_kernel(Server,Mod,Fun,Args,Init),
  24. eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
  25. eval_script([{apply,{Mod,Fun,Args}}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) ->
  26. ...
  27. eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice);
  28. eval_script([],_,_,_,_,_,_,_) ->
  29. ok;
  30. eval_script(What,_,_,_,_,_,_,_) ->
  31. exit({'unexpected command in bootfile',What}).

eval_script/8对BootList中的每一个动作进行处理。有一些动作要给init进程发送消息,init进程的boot_loop/2循环接收这些消息。boot_loop/2接收的消息中有以下两个:

  1. boot_loop(BootPid, State) ->
  2. receive
  3. {BootPid,progress,started} ->
  4. {InS,_} = State#state.status,
  5. notify(State#state.subscribed),
  6. boot_loop(BootPid,State#state{status = {InS,started},
  7. subscribed = []});
  8. ......
  9. {'EXIT',BootPid,normal} ->
  10. {_,PS} = State#state.status,
  11. notify(State#state.subscribed),
  12. loop(State#state{status = {started,PS},
  13. subscribed = []});
  14. ......
  15. end.

BootList最后一条指令是{progress,started},对应了boot_loop/2第3行的消息,在执行完这一条指令之后,eval_script/8结束了执行,因此do_boot/3在结束eval_script/8之后调用start_em/1之后就正常退出了,进程<0.2.0>正常退出,boot_loop/2收到'EXIT'消息,init进程进入loop/1循环。此时,init作为初始化的任务已经完成。这一个默认的启动脚本启动了两个应用程序,kernel和STDLIB,前者是一个普通应用程序,后者只是一个库应用程序。如果erl没有传入-noshell参数,kernel还会启动shell和用户交互。这两个应用程序是Erlang最简系统的基础,前者提供了必要的系统服务,例如文件服务、网络服务和错误日志记录服务等,后者提供了编写程序需要使用的各种工具、数据结构以及OTP相关的重要模块。

小结

通过本文的分析可以看出,Erlang虚拟机很像一个运行了操作系统的计算机。erl对应的是BIOS,加载对应bootloader的erlexec。erlexec加载BEAM虚拟机,BEAM虚拟机对应了操作系统。接下来BEAM进行初步的初始化,初始化执行环境,对应了操作系统的初始化。初始化完成之后,BEAM像Linux一样加载系统中的第一个进程init。init进程读取启动列表,执行启动系统的步骤。执行完这些步骤之后,Erlang成为了一个完全完成了初始化过程可以运行的系统。Erlang像操作系统一样,有自己的调度系统,内存管理系统,还有和外界交互的I/O系统。只不过内存管理系统更加的智能,可以主动帮助进程进行垃圾回收。I/O系统以系统服务的方式存在,通过Erlang消息通信的方式向其他进程提供服务,因此Erlang的进程只需要通过消息这一种语义就能和外界交换数据。Erlang中的模块就好像操作系统中的动态共享库,只要加载到系统中,就可以供所有的进程访问。多个模块可以组织为应用程序。Erlang的模块命名是平坦的,因此不同应用程序中的模块不能重名。Erlang的应用程序是对模块和进程的一种组织方式,从一个应用程序可以包含一组进程的角度看,Erlang的应用程序有点类似于Linux系统中的进程。

原文:http://www.cnblogs.com/zhengsyao/archive/2012/08/15/Erlang-otp_start_up.html

Erlang虚拟机的启动的更多相关文章

  1. erlang虚拟机代码执行原理

     转载:http://blog.csdn.NET/mycwq/article/details/45653897 erlang是开源的,很多人都研究过源代码.但是,从erlang代码到c代码,这是个不小 ...

  2. erlang虚拟机代码运行原理

    erlang是开源的,非常多人都研究过源码.可是.从erlang代码到c代码.这是个不小的跨度.并且代码也比較复杂. 所以这里,我利用一些时间,整理下erlang代码的运行过程.从erlang代码编译 ...

  3. erlang应用程序启动

    (1)erlang应用程序启动过程中,还可以分阶段启动.          在erlang应用程序的资源文件*.app可以定义分步骤启动.            *.app中的start_phase字 ...

  4. Profile 分析 Erlang 虚拟机源码时要注意的一个问题

    最近用 Intel Vtune 剖析 Erlang 虚拟机的运行,想看看那些函数和语句耗时最多,遇到一个小问题,那就是 Vtune 给出的源码和汇编码对应有问题.这个问题在 profile 或 deb ...

  5. Dalvik虚拟机的启动过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8885792 在Android系统中,应用程序进 ...

  6. Windows设置VMware开机自动启动,虚拟机也启动

    很多用windows系统电脑开发的童鞋,会在自己电脑上装一个虚拟机,然后在装一个linux系统当作服务器来使用.但每次电脑开机都要去重启一下虚拟机电源,实在是不划算.下面博主教大家在windows系统 ...

  7. 如何设置Java虚拟机JVM启动内存参数

    Tomcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数.具体设置方法为: Tomcat修 ...

  8. 【转】Dalvik虚拟机的启动过程分析

    在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的.Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程 ...

  9. java虚拟机jvm启动后java代码层面发生了什么&quest;

    java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...

随机推荐

  1. BIT 树状数组 详解 及 例题

    (一)树状数组的概念 如果给定一个数组,要你求里面所有数的和,一般都会想到累加.但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组 ...

  2. mysql mHA manager 状态修改

    启动:nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_fail ...

  3. javascript基础08

    发现今天居然没有要写,那我就写写之前做的笔记吧. 这是事件的深入: 拖拽逻辑: 第一个: onmousedown : 选择元素 第二个: onmousemove : 移动元素 第三个:onmouseu ...

  4. Fluentd 例子

    0. 安装.启动 安装 curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh 更新:最近貌似会安装 ...

  5. API读取和处理的文件

    1.FileList对象  FileList对象是File对象的一个集合,设置multiple就可以多文件上传.2.Blob对象 Blob对象就是一个二进制原始数据对象,它提供了slice方法可以读取 ...

  6. UIResponder

    原网址:http://www.cnblogs.com/kuku/archive/2011/11/12/2246389.html 在 iOS 中,一个 UIResponder 对象表示一个可以接收触摸屏 ...

  7. Java解析XMl文件之SAX和DOm方法

    如题,这两种方法的jar包都在jdk中,不需要下载. 先来说下目录结构: 首先建一个Peron类封装person.xml的属性:DomParseService和SaxParseService分别为两种 ...

  8. 高吞吐高并发Java NIO服务的架构(NIO架构及应用之一)

    高吞吐高并发Java NIO服务的架构(NIO架构及应用之一) http://maoyidao.iteye.com/blog/1149015   Java NIO成功的应用在了各种分布式.即时通信和中 ...

  9. Solve Longest Path Problem in linear time

    We know that the longest path problem for general case belongs to the NP-hard category, so there is ...

  10. android sdk 如何重新生成debug&period;keystore

    1)首先你要确定你安装的JDK位置,Windows->Preferences->Java->Installed JREs,你可以看到是Jre的location,再在dos cmd模式 ...