所以这里,我利用一些时间,整理下erlang代码的运行过程。从erlang代码编译过程,到代码运行过程做解说。然后重点讲下虚拟机运行代码的原理。
将本篇文章。献给全部喜欢erlang的人。当模块载入后,在erlang shell下通过下面方式能够获取模块的运行时代码。就会生成test.dis文件
有论文说是为了降低Beam的大小,这点我没有做过实质性的探究,我仅仅是认为有限指令集比較短,更easy阅读被人理解。关于有限指令集和扩展指令集的区别。我在文章最后的拓展阅读做了讨论。
文章erlang版本号以R16B02作说明。
| File  | Path | 
| beam_makeops | erts/emulator/utils/ | 
| ops.tab | erts/emulator/beam/ | 
| beam_opcodes.c | erts/emulator/<machine>/opt/smp/ | 
| beam_load.c | erts/emulator/beam/ | 
| genop.tab | lib/compiler/src/ | 
ERTS是erlang VM最底层的应用,负责和操作系统交互,管理I/O,实现erlang进程和BIF函数。BEAM模拟器是运行Erlang程序经编译后产出的字节码的地方。
后来改成基于寄存器的虚拟机,也就是如今的BEAM(Bogdan‘s Abstract Machine),运行效率有了较大幅度提升。这在Joe的erlang VM演变论文有说到。
而基于寄存器(register-based)的指令长度不是固定的,能够在指令中带多个操作数。这样,基于寄存器能够降低指令数量,降低入栈出栈操作,从而降低了指令派发的次数和内存訪问的次数,相比开销少了非常多。
可是,假设利用寄存器做数据交换,就要常常保存和恢复寄存器的结果。这就导致基于寄存器的虚拟机在实现上要比基于栈的复杂,代码编译也要复杂得多
假设进程在等待新消息时也会被挂起,直到这个进程接收到新消息后。就又一次加到调度队列。
栈被用来存储简单的数据,还有指向堆中复杂数据的数据指针。
栈有指针指向堆,但不会有指针从堆到栈。
为此,基于寄存器的虚拟机使用暂时变量来保存这个本地变量,这个暂时变量也就是寄存器。并且,这个寄存器变量通常都被优化成CPU的寄存器变量,这样,虚拟机訪问寄存器变量甚至都不用訪问内存。极大的提高了系统的运行速度。
    /*
     * X register zero; also called r(0)
     */
    register Eterm x0 REG_x0 = NIL;register修饰符的作用是暗示编译器。某个变量将被频繁使用,尽可能将其保存在CPU的寄存器中,以加快其存储速度。随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时。如今的编译器能比程序猿做出更好的决定,往往会忽略register修饰符。可是就erlang虚拟机对寄存器变量的使用程度,应该是能够利用到CPU寄存器的优点。
当进程被调出的时候,寄存器就给其它进程使用。(进程切换保存进程上下文时。仅仅须要保存指令寄存器IP和当前函数信息。效率非常高)
while(1){
 opcode = *vPC++;
 switch(opcode){
   case i_call_fun:
          ..
       break;
   case call_bif_e:
          ..
       break;
 //and many more..
 }
};字节码在虚拟机中运行。运行过程相似CPU运行指令过程,分为取指,解码。运行3个过程。通常情况下,每一个操作码相应一段处理函数,然后通过一个无限循环加一个switch的方式进行分派。start()->
   spawn(fun() -> fun1(1) end).  %% 创建进程。运行 fun1/1
fun1(A) ->
   A1 = A + 1,
   B = trunc(A1),  %% 运行 trunc/1
   {ok, A1+B}.以上。进程在运行函数( trunc/1)调用前。会将当前的本地变量和返回地址指针CP写入栈。然后,在运行完这个函数(trunc/1)后再从栈取出CP指令和本地变量,依据CP指针返回调用处,继续运行后面的代码。假设是少量的switch case,全然能够接受,可是对于虚拟机来说。有着成百上千的switch case,并且运行频繁非常高,运行一条指令就须要一次线性搜索。确定比較耗性能。假设能直接跳转到运行代码位置,就能够省去线性搜索的过程了。于是在字节码的分派方式上,做了新的改进。这项技术叫作
Context Threading(上下文线索化技术。Thread眼下都没有合适的中文翻译。我这里意译为线索化。表示当中的线索关系)。Export* bif_export[BIF_SIZE];
BifEntry bif_table[] = {
    {am_erlang, am_abs, 1, abs_1, abs_1},
    {am_erlang, am_adler32, 1, adler32_1, wrap_adler32_1},
    {am_erlang, am_adler32, 2, adler32_2, wrap_adler32_2},
    {am_erlang, am_adler32_combine, 3, adler32_combine_3, wrap_adler32_combine_3},
    {am_erlang, am_apply, 3, apply_3, wrap_apply_3},
    {am_erlang, am_atom_to_list, 1, atom_to_list_1, wrap_atom_to_list_1},typedef struct bif_entry {
    Eterm module;
    Eterm name;
    int arity;
    BifFunction f;  // bif函数
    BifFunction traced;  // 函数调用跟踪函数
} BifEntry;erlang BEAM模拟器启动时会初始化bif函数表,init_emulator:
{
     
     em_call_error_handler = OpCode(call_error_handler);
     em_apply_bif = OpCode(apply_bif);
     beam_apply[0] = (BeamInstr) OpCode(i_apply);
     beam_apply[1] = (BeamInstr) OpCode(normal_exit);
     beam_exit[0] = (BeamInstr) OpCode(error_action_code);
     beam_continue_exit[0] = (BeamInstr) OpCode(continue_exit);
     beam_return_to_trace[0] = (BeamInstr) OpCode(i_return_to_trace);
     beam_return_trace[0] = (BeamInstr) OpCode(return_trace);
     beam_exception_trace[0] = (BeamInstr) OpCode(return_trace); /* UGLY */
     beam_return_time_trace[0] = (BeamInstr) OpCode(i_return_time_trace);
     /*
      * Enter all BIFs into the export table.
      */
     for (i = 0; i < BIF_SIZE; i++) {
         ep = erts_export_put(bif_table[i].module, //模块名
         bif_table[i].name,
         bif_table[i].arity);
         bif_export[i] = ep;
         ep->code[3] = (BeamInstr) OpCode(apply_bif);
         ep->code[4] = (BeamInstr) bif_table[i].f;  // BIF函数
         /* XXX: set func info for bifs */
         ep->fake_op_func_info_for_hipe[0] = (BeamInstr) BeamOp(op_i_func_info_IaaI);
     }/*
 * 下面截取 bif 处理过程
 */
OpCase(call_bif_e):
    {
 Eterm (*bf)(Process*, Eterm*, BeamInstr*) = GET_BIF_ADDRESS(Arg(0)); // 依据參数获取bif实际运行函数
 Eterm result;
 BeamInstr *next;
 PRE_BIF_SWAPOUT(c_p);
 c_p->fcalls = FCALLS - 1;
 if (FCALLS <= 0) {
    save_calls(c_p, (Export *) Arg(0));
 }
 PreFetch(1, next);
 ASSERT(!ERTS_PROC_IS_EXITING(c_p));
 reg[0] = r(0);
 result = (*bf)(c_p, reg, I); // 运行bif函数
 ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
 ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
 ERTS_HOLE_CHECK(c_p);
 ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p);
 PROCESS_MAIN_CHK_LOCKS(c_p);
 if (c_p->mbuf || MSO(c_p).overhead >= BIN_VHEAP_SZ(c_p)) {
     Uint arity = ((Export *)Arg(0))->code[2];
     result = erts_gc_after_bif_call(c_p, result, reg, arity);
     E = c_p->stop;
 }
 HTOP = HEAP_TOP(c_p);
 FCALLS = c_p->fcalls;
 if (is_value(result)) {
     r(0) = result;
     CHECK_TERM(r(0));
     NextPF(1, next);
 } else if (c_p->freason == TRAP) { 
     SET_CP(c_p, I+2);
     SET_I(c_p->i);
     SWAPIN;
     r(0) = reg[0];
     Dispatch();
}上面涉及到一个宏,就是取得bif函数地址。#define GET_BIF_ADDRESS(p) ((BifFunction) (((Export *) p)->code[4]))依据前面提到的。((Export *) p)->code[4] 就是 bif_table表的中BIF函数的地址。
| Type | Description | 
|---|---|
| t | An arbitrary term, e.g. {ok,[]} | 
| I | An integer literal, e.g. 137 | 
| x | A register, e.g. R1 | 
| y | A stack slot | 
| c | An immediate term, i.e. atom/small int/nil | 
| a | An atom, e.g. ‘ok‘ | 
| f | A code label | 
| s | Either a literal, a register or a stack slot | 
| d | Either a register or a stack slot | 
| r | A register R0 | 
| P | A unsigned integer literal | 
| j | An optional code label | 
| e | A reference to an export table entry | 
| l | A floating-point register | 
原文:http://www.cnblogs.com/gavanwanggw/p/6963695.html