When analyzing Erlang:send's Bif, a Bif_trap series of macros was found. Refer to some of Erlang's own descriptions, which are designed to implement a mechanism called trap. The trap mechanism introduces Erlang's code directly into ERTs, allowing the C function to "use" these Erlang functions directly.
Let's first think about why Erlang implements the trap mechanism. Let me start with the more recent fire go, the go itself is compiled and erlang this type of opcode interpretation of the nature is different. Many of the functions in Go runtime are also implemented in C, in order to glue and go code and C code, go runtime uses a lot of assembly to manipulate the go function stack and C language stack. At the same time, in order to go to the collaborative thread switch, but also use a lot of assembly language to modify the Go function stack. This requires that the runtime writers are familiar with the C compiler, the corresponding platform of the hardware ABI is quite familiar with, but also the key is greatly dispersed the runtime author's energy, can not let the runtime author's energy in garbage collection and scheduling. On the other hand, we can also analyze why it is very difficult to achieve the soft real-time, like Erlang, a fair dispatch.
Erlang implements the trap mechanism, and I personally think there are several reasons for this:
The more difficult function of C function is implemented by Erlang, which is directly introduced into ERTs.
Deferred execution, with driver-related operations, or decisions that need to be made through the OTP library, is given to Erlang.
Actively abandon the CPU, let the dispatch schedule again. This equivalent to let BIF support yield, to prevent the C function execution time is too long, can not guarantee soft real-time fair scheduling.
How does Erlang implement the trap mechanism? The trap mechanism for Erlang is done by using the trap function, Bif_trap macros and schedulers. Let me take a erlang:send of this bif and Beam_emu part of the code to trap the process.
Let's take a look at the code that goes into BIF:
opcase (call_bif_e): { eterm (*BF) (process*, eterm*, beaminstr *) = get_bif_address (ARG (0)); 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) ; 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 Mbuf is not empty and overhead has exceeded the size of the binary heap, a garbage collection is required if (c_p->mbuf | | &NBSP;MSO (c_p). OVERHEAD&NBSP;>=&NBSP;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;// See if it's straight. Results if (Is_value (Result)) { r (0) = result; check_term (R ( 0)); &NBSP;&NBSP;NEXTPF (1, next);//no result, return to the_non_value } else if (C_p->freason == trap) {//set the continuation point of the process &NBSP;&NBSP;SET_CP (c_p, i+2);//settings Change Scheduler the executing instruction set_i ( C_p->i);//re-entry, update fast deposit swapin; r (0) = reg[0]; dispatch (); }
All Erlang code will generate a call_bif_e erts instruction when it calls the BIF operation. When the scheduler executes to this command, first to find the address of the BIF function, and then through the C language call execution bif get result, and according to the convention if result exists directly into the fast deposit x0 (r (0)) and then continue to execute, If there is no return value and Freason is the trap, then we trigger the trap mechanism.
Let's take a look at some of the Erl_send code.
switch (Result) { case 0:/* may need to yield even though we do not bump reds here... */ if (Erts_is_proc_out_of_reds (P)) goto yield_return; bif_ret (msg); BREAK;&NBSP;&NBSP;&NBSP;&NBSP;CASE&NBSP;SEND_TRAP:&NBSP;BIF_TRAP2 (DSEND2_TRAP,&NBSP;P,&NBSP;TO,&NBSP;MSG); break; case send_yield: erts_bif_yield2 (Bif_export[BIF_send_2], &NBSP;P,&NBSP;TO,&NBSP;MSG); break; case send_yield_return: yield_return: erts_bif_yield_return (p, msg); case send_await_ Result: assert (Is_internal_ref (ref)); &NBSP;BIF_TRAP3 (await_port_send_result_trap, p, ref, MSG,&NBSP;MSG); case send_badarg: bif_error (p, badarg); break ; caSe send_user_error: bif_error (p, exc_error); break; case send_internal_error: bif_error (P, exc_internal_error); break; Default: assert (! "Illegal send result"); break; }
We can see that there are many macros used in this bif_trap, so what does this macro do? This is a very simple macro.
#define BIF_TRAP2 (TRAP_, p, A0, A1) do {eterm* reg = Erts_proc_get_schdata ((p))->x_reg_array; (p)->arity = 2; Reg[0] = (A0); REG[1] = (A1); (p)->i = (beaminstr*) ((TRAP_)->addressv[erts_active_code_ix ()]); (p)->freason = TRAP; return the_non_value; } while (0)
is to secretly change the Erlang process's instruction I, and directly let the function return The_non_value.
This time someone will probably say, this is not chaos, secretly changed the Erlang process execution instructions, then this code execution, how can go back to the original module code it. We can go back to the scheduler code again, we can see that the scheduler's global instruction I is also the code of the module being executed, the scheduler found the existence of traps, the process of the continuation of the CP (the equivalent Erlang function of the return address) directly to I+2 is the original module in the next instruction, The global directive I is then set to the Erlang process instruction I, followed by execution. From the trap macro, it is not difficult to see what the trap function is, it is an export data structure.
Finally, let's analyze why Erlang implements the trap in this way. The main reason is that erlang is opcode interpreted, and the process executed by the Erlang process is controllable. Another reason is that it is much better to use the C compiler to do the stack and stack operation of C functions, and it is not necessary to write platform-related assembly code to manipulate C stacks.
Let's talk about the trap mechanism of Erlang.