Joe Armstrong在描述Erlang的设计要求时,就提到了软件维护应该能在不停止系统的情况下进行。在实践中,我们也因为这种不停止服务的热更新获益良多。那么Erlang是如何做到热更新的呢?这就是本文要讨论的问题。
在前面的文章也说到了。erlang VM为每个模块最多保存2份代码,当前版本'current'和旧版本'old',当模块第一次被加载时,代码就是'current'版本。如果有新的代码被加载,'current'版本代码就变成了'old'版本,新的代码就成了'current'版本。erlang用两个版本共存的方法来保证任何时候总有一个版本可用,对外服务就不会停止。
前言
为什么代码热更新时不影响进程运行?
为什么进程要使用外部调用(M:F/A)才能切换到新代码?
为什么可以同时使用2个版本的代码?
为什么只能一个模块一个模块热更?
....
我们总会有很多疑问,但一切的答案都在源码上。现在深入剖析下erlang热更新实现机制,相信你的疑惑可以找到答案。
源码剖析
以下是erlang热更新的三个过程:
c(Mod) ->
compile:file(Mod), %% 编译erl成beam文件
code:purge(Mod), %% 清理模块(同时杀掉运行'old'代码的进程,'current'的不受影响)
code:load_file(Mod). %% 加载beam代码到vm
热更新加载beam代码到vm,这一步是调用了 erlang:load_module() 实现,文章重点说下这个函数。(以R16B02作说明)
%% erlang:load_module/2
load_module(Mod, Code) ->
case erlang:prepare_loading(Mod, Code) of
{error,_}=Error ->
Error;
Bin when erlang:is_binary(Bin) ->
case erlang:finish_loading([Bin]) of
ok ->
{module,Mod};
{Error,[Mod]} ->
{error,Error}
end
end.
以上主要是2个过程:
1、 erlang:prepare_loading() 预加载beam的操作,是一个解析beam的过程
2、 erlang:finish_loading() 实现代码加载到vm的过程
预加载beam
现在看下erlang:prepare_loading() ,这是个bif函数,实现预加载beam:
/*
* beam_bif_load.c prepare_loading_2函数,实现 erlang:prepare_loading()
*/
BIF_RETTYPE prepare_loading_2(BIF_ALIST_2)
{
byte* temp_alloc = NULL;
byte* code;
Uint sz;
Binary* magic;
Eterm reason;
Eterm* hp;
Eterm res;
if (is_not_atom(BIF_ARG_1)) {
error:
erts_free_aligned_binary_bytes(temp_alloc);
BIF_ERROR(BIF_P, BADARG);
}
// 复制原始的beam文件数据
if ((code = erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc)) == NULL) {
goto error;
}
magic = erts_alloc_loader_state();
sz = binary_size(BIF_ARG_2);
// 预加载beam(解析beam,加载数据,生成导出函数)
reason = erts_prepare_loading(magic, BIF_P, BIF_P->group_leader,
&BIF_ARG_1, code, sz);
// 释放beam数据空间
erts_free_aligned_binary_bytes(temp_alloc);
if (reason != NIL) {
hp = HAlloc(BIF_P, 3);
res = TUPLE2(hp, am_error, reason);
BIF_RET(res);
}
hp = HAlloc(BIF_P, PROC_BIN_SIZE);
res = erts_mk_magic_binary_term(&hp, &MSO(BIF_P), magic);
erts_refc_dec(&magic->refc, 1);
BIF_RET(res);
}
下面是解析beam的过程:
/*
* beam_load.c erts_prepare_loading函数,实现beam解析,加载数据,生成导出函数
*/
Eterm erts_prepare_loading(Binary* magic, Process *c_p, Eterm group_leader,
Eterm* modp, byte* code, Uint unloaded_size)
{
Eterm retval = am_badfile;
LoaderState* stp;
stp = ERTS_MAGIC_BIN_DATA(magic);
stp->module = *modp;
stp->group_leader = group_leader;
#if defined(LOAD_MEMORY_HARD_DEBUG) && defined(DEBUG)
erts_fprintf(stderr,"Loading a module\n");
#endif
/*
* Scan the IFF file.
*/
CHKALLOC();
CHKBLK(ERTS_ALC_T_CODE,stp->code);
// 检查beam文件格式,生成模块相关信息
if (!init_iff_file(stp, code, unloaded_size) ||
!scan_iff_file(stp, chunk_types, NUM_CHUNK_TYPES, NUM_MANDATORY) ||
!verify_chunks(stp)) {
goto load_error;
}
/*
* 读取代码块头部信息,检查版本支持,获取label和函数个数
*/
CHKBLK(ERTS_ALC_T_CODE,stp->code);
define_file(stp, "code chunk header", CODE_CHUNK);
if (!read_code_header(stp)) {
goto load_error;
}
/*
* 初始化代码信息
*/
stp->code_buffer_size = 2048 + stp->num_functions;
stp->code = (BeamInstr *) erts_alloc(ERTS_ALC_T_CODE,
sizeof(BeamInstr) * stp->code_buffer_size);
stp->code[MI_NUM_FUNCTIONS] = stp->num_functions;
stp->ci = MI_FUNCTIONS + stp->num_functions + 1;
stp->code[MI_ATTR_PTR] = 0;
stp->code[MI_ATTR_SIZE] = 0;
stp->code[MI_ATTR_SIZE_ON_HEAP] = 0;
stp->code[MI_COMPILE_PTR] = 0;
stp->code[MI_COMPILE_SIZE] = 0;
stp->code[MI_COMPILE_SIZE_ON_HEAP] = 0;
/*
* 读取原子表
*/
CHKBLK(ERTS_ALC_T_CODE,stp->code);
define_file(stp, "atom table", ATOM_CHUNK);
if (!load_atom_table(stp)) {
goto load_error;
}
/*
* 读取导入函数表
*/
CHKBLK(ERTS_ALC_T_CODE,stp->code);