BEAM虚拟机之浅显易懂

BEAM是Bogdan/Björn Erlang Abstract machine的缩写。它是寄存器虚拟机,不过它也有栈。

寄存器和调用

作为一个寄存器虚拟机就意味着在函数调用的时候不是通过栈而是用寄存器来传递参数。它们与我们所理解的CPU架构中的寄存器不一样,而只是一个Erlang数据结构的值所组成的数组。这样的寄存器有1024个,但是有这么多入参的函数几乎不存在,所以通常虚拟机仅仅使用了前面3到10个寄存器。

例如,当调用一个有三个入参的函数的时候,寄存器x0,x1和x2分别被设置为三个入参值。如果我们需要进行递归调用,我们将不得不改写寄存器并丢失它们的原始值。在这种情况下,我们将需要保存旧的值到栈中。除了栈帧外没有动态栈分配,这是由编译器决定的。

每一个进程有自己的寄存器集。

指令

BEAM指令集包含大约158个基本指令,它们中的大部分可以通过调用 erlc -S yourfile.erl 生成汇编文件来查看到。每一个指令有零或多个参数。不同的技巧用来在便携方式和紧凑方式编码参数。例如,代码中的位置定位(也叫做标签)是用它们的索引来编码的,并且代码装载器后续会把它们翻译成内存地址。

在BEAM代码装载过程中,一些混合的操作码被更快的操作码替换。这个优化的技巧叫做超级指令。对于每一个操作码,在beam_emu.c文件中都有一个C标签对应的一段代码。一个C标签组成的数组存储在同一个虚拟机循环例程的尾部并且被当作查询表来使用。

请参见:
详细解释:BEAM文件格式
详细解释:BEAM指令代码

线程虚拟机循环

虚拟机模拟器循环在 emulator/beam/beam_emu.s 里包含许多代码小片段,每一个代码小片段有一个标签和处理一个BEAM指令。它们都属于一个非常长的函数。一个标签表存储在此相同函数里,它被用来作为查询表。

装载操作码被标签地址替换后,紧跟着它们的是参数。例如,操作码 #1,一个来自标签数组第一索引元素被置于在代码内存。

这种方式很容易跳转到处理下一个操作码的 C 代码中的某个位置。只需要读一个 void* 指针并执行一个goto *p 的动作。此功能是C和C++编译器的一个扩展。这种类型的虚拟机循环称为直接线程调度虚拟机循环。

其他虚拟机循环的类型有:

  • 切换调度虚拟机(如果C编译器拒绝支持标签扩展,则这种类型被BEAM虚拟机源使用)。它也比直接线程调度虚拟机慢20%至30%;
  • 直接调用线程
  • 其他

原文链接: http://beam-wisdoms.clau.se/en/latest/eli5-vm.html