因此這裏,我利用一些時間,整理下erlang代碼的運行過程。從erlang代碼編譯過程,到代碼運行過程作解說。而後重點講下虛擬機運行代碼的原理。將本篇文章。獻給所有喜歡erlang的人。html
當模塊載入後,在erlang shell下經過下面方式可以獲取模塊的運行時代碼。就會生成test.dis文件c++
有論文說是爲了下降Beam的大小,這點我沒有作過實質性的探究,我僅僅是認爲有限指令集比較短,更easy閱讀被人理解。關於有限指令集和擴展指令集的區別。我在文章最後的拓展閱讀作了討論。web
文章erlang版本號以R16B02做說明。shell
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)的指令長度不是固定的,可以在指令中帶多個操做數。這樣,基於寄存器可以下降指令數量,下降入棧出棧操做,從而下降了指令派發的次數和內存訪問的次數,相比開銷少了很是多。併發
但是,假設利用寄存器作數據交換,就要經常保存和恢復寄存器的結果。這就致使基於寄存器的虛擬機在實現上要比基於棧的複雜,代碼編譯也要複雜得多app
假設進程在等待新消息時也會被掛起,直到這個進程接收到新消息後。就又一次加到調度隊列。ide
棧被用來存儲簡單的數據,還有指向堆中複雜數據的數據指針。函數
棧有指針指向堆,但不會有指針從堆到棧。
爲此,基於寄存器的虛擬機使用暫時變量來保存這個本地變量,這個暫時變量也就是寄存器。而且,這個寄存器變量一般都被優化成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 |