erlang虚拟机代码运行原理
所以这里,我利用一些时间,整理下erlang代码的运行过程。从erlang代码编译过程,到代码运行过程做解说。然后重点讲下虚拟机运行代码的原理。将本篇文章。献给全部喜欢erlang的人。
erlang代码编译过程
1. erlang核心代码
2. erlang汇编码
3. erlang BEAM
4. erlang运行时代码
当模块载入后,在erlang shell下通过下面方式能够获取模块的运行时代码。就会生成test.dis文件
有论文说是为了降低Beam的大小,这点我没有做过实质性的探究,我仅仅是认为有限指令集比較短,更easy阅读被人理解。关于有限指令集和扩展指令集的区别。我在文章最后的拓展阅读做了讨论。
erlang代码从编译到运行过程
文章erlang版本号以R16B02作说明。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXljd3E=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
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/
|
erlang 虚拟机运行代码的原理
erlang虚拟机概述
ERTS是erlang VM最底层的应用,负责和操作系统交互,管理I/O,实现erlang进程和BIF函数。BEAM模拟器是运行Erlang程序经编译后产出的字节码的地方。
后来改成基于寄存器的虚拟机,也就是如今的BEAM(Bogdan's Abstract Machine),运行效率有了较大幅度提升。这在Joe的erlang VM演变论文有说到。
而基于寄存器(register-based)的指令长度不是固定的,能够在指令中带多个操作数。这样,基于寄存器能够降低指令数量,降低入栈出栈操作,从而降低了指令派发的次数和内存訪问的次数,相比开销少了非常多。
可是,假设利用寄存器做数据交换,就要常常保存和恢复寄存器的结果。这就导致基于寄存器的虚拟机在实现上要比基于栈的复杂,代码编译也要复杂得多
erlang进程
假设进程在等待新消息时也会被挂起,直到这个进程接收到新消息后。就又一次加到调度队列。
进程的栈和堆
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXljd3E=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
堆用于存储复杂的数据结构,如元组。列表或大整数。
栈被用来存储简单的数据,还有指向堆中复杂数据的数据指针。
栈有指针指向堆,但不会有指针从堆到栈。
寄存器
为此,基于寄存器的虚拟机使用暂时变量来保存这个本地变量,这个暂时变量也就是寄存器。并且,这个寄存器变量通常都被优化成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指针返回调用处,继续运行后面的代码。
Threaded Code(线索化代码)
假设是少量的switch case,全然能够接受,可是对于虚拟机来说。有着成百上千的switch case,并且运行频繁非常高,运行一条指令就须要一次线性搜索。确定比較耗性能。假设能直接跳转到运行代码位置,就能够省去线性搜索的过程了。于是在字节码的分派方式上,做了新的改进。这项技术叫作 Context Threading(上下文线索化技术。Thread眼下都没有合适的中文翻译。我这里意译为线索化。表示当中的线索关系)。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXljd3E=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
拓展阅读
BIF(内建函数)
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);
}
下面写个简单的样例说明。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbXljd3E=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
/*
* 下面截取 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 |
erlang虚拟机代码运行原理的更多相关文章
- erlang虚拟机代码执行原理
转载:http://blog.csdn.NET/mycwq/article/details/45653897 erlang是开源的,很多人都研究过源代码.但是,从erlang代码到c代码,这是个不小 ...
- java虚拟机的运行原理
一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘当中.然后你在命令行中输入 javac YourClassNam ...
- Profile 分析 Erlang 虚拟机源码时要注意的一个问题
最近用 Intel Vtune 剖析 Erlang 虚拟机的运行,想看看那些函数和语句耗时最多,遇到一个小问题,那就是 Vtune 给出的源码和汇编码对应有问题.这个问题在 profile 或 deb ...
- Nodejs的运行原理-科普篇
前言 Nodejs目前处境稍显尴尬,很多语言都已经拥有异步非阻塞的能力.阿里的思路是比较合适的,但是必须要注意,绝对不能让node做太多的业务逻辑,他只适合接收生成好的数据,然后或渲染后,或直接发送到 ...
- 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制
[原创]分布式之数据库和缓存双写一致性方案解析(三) 正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...
- 洗礼灵魂,修炼python(3)--从一个简单的print代码揭露编码问题,运行原理和语法习惯
前期工作已经准备好后,可以打开IDE编辑器了,你可以选择python自带的IDLE,也可以选择第三方的,这里我使用pycharm--一个专门为python而生的IDE 按照惯例,第一个python代码 ...
- python虚拟机运行原理
近期为了面试想要了解下python的运行原理方面的东西,奈何关于python没有找到一本类似于深入理解Java虚拟机方面的书籍,找到了一本<python源码剖析>电子书,但是觉得相对来说最 ...
- python3 源码阅读-虚拟机运行原理
阅读源码版本python 3.8.3 参考书籍<<Python源码剖析>> 参考书籍<<Python学习手册 第4版>> 官网文档目录介绍 Doc目录主 ...
- 理解Web应用程序的代码结构和运行原理(3)
1.理解Web应用程序的运行原理和机制 Web应用程序是基于浏览器/服务器模式(也称B/S架构)的应用程序,它开发完成后,需要部署到Web服务器上才能正常运行,与用户交互的客户端是网页浏览器. 浏览器 ...
随机推荐
- MAC下搭建appium UI自动化环境
参考资料: http://qa.blog.163.com/blog/static/190147002201510161119832/ http://blog.csdn.net/liuchunming0 ...
- python 异步IO
参考链接:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573 ...
- ArcGIS api for javascript——图形-增加图形到地图
描述 本例展示了如何使用Draw工具栏在地图上描绘许多种类的几何体.ArcGIS JavaScript API包含工具栏. 工具栏不是一个在页面上自动地可见的用户界面组件.相反,工具栏是一个助手类,可 ...
- 百度地图SDK for Android【Demo兴趣点搜索】
百度地图SDK为开发人员提供了便捷的检索服务. 今天我将为大家介绍Poi检索相关的内容. 首先,我们要构建一个最主要的地图应用.详细介绍请參考:百度地图SDK for Android[ ...
- JVM-java字符编码
在JVM内部,所有的字符都是用Unicode编码的.而对于JVM所在操作系统的文件系统,可能有不同的编码类型. 由于JVM和OS文件系统所使用的编码方式不同,JVM在与操作系统进行数据交互的时候,就会 ...
- BZOJ 3781 莫队
思路:不能再裸的裸题-- //By SiriusRen #include <cmath> #include <cstdio> #include <algorithm> ...
- HDU 1512 左偏树+并查集
思路: 左偏树里面掺了一些并查集的应用 这里放一份左偏树的代码模板 重点就是merge函数了-- int merge(int k1,int k2){ if(!k1||!k2)return k1+k2; ...
- AnkhSvn介绍 插件
转载:http://www.cnblogs.com/lyhabc/articles/2483011.html AnkhSVN是一款在VS中管理Subversion的插件,您可以在VS中轻松的提交.更新 ...
- msiexec
msiexec: runCmd = new String[]{ "msiexec", "/i", exeName, "/quiet", &q ...
- python多线程理解
在发送网络请求的过程中,单个请求的速度总是有着很大的限制,而任务往往需要以更快的速度去执行,这时多线程就是一个很好地选择.python已经给我们封装好了多线程库thread和threading. th ...