分析erlang热更新实现机制

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);
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值