Erlang热部署 – 模块更新
Erlang的热部署做的很完善,参见ReleaseHandling,这篇文章只关心最基本的模块更新。模块是Erlang程序组织的最基本单元。如下代码就是一个最简单的hello模块(为了说明问题,我们添加了一个init函数):
?
-module(hello).-export([init/0, hello/1]).init() -> Db = dict:new(), dict:store(name, "jzh", Db).hello(Db) -> Value = dict:fetch(name, Db),io:format("hello: ~p!~n", [Value]).?
?运行一下:
?
1> c(hello).{ok,hello}2> Db = hello:init().{dict,1,16,16,8,80,48, {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]} {{[],[],[],[],[], [[name,106,122,104]], [],[],[],[],[],[],[],[],[],[]}}}3> hello:hello(Db).hello: "jzh"!ok?(c命令的作用是编译和加载指定模块)
?
4> c(hello).{ok,hello}5> hello:hello(Db).hi: "jzh"!ok
?可以看到,代码更新后打印出hi字样(保存在dict中的name,并没有因为新代码的加载而失效)。
?
-module(m).-export([loop/0]).loop() -> receive code_switch -> m:loop(); Msg -> ... loop() end.
?如上代码,如果有代码更新,并且进程没有收到code_switch消息,那么进程就会通过loop调用一直在引用旧版本代码;而收到code_switch消息后,进程便通过m:loop引用到当前版本代码。
?
-module(test).-import(hello, [hello/0]).test2() ->ok.test() ->test2(),test:test2(),hello(),hello:hello(),ok.
?
?我们来看看test函数生成的opcode:
?
i_call_f test:test2/0i_call_ext_e test:test2/0i_call_ext_e hello: hello/0i_call_ext_e hello: hello/0
可以看出,直接调用test2生成的opcode是i_call_f,而其它的全限定调用(包括hello()调用)都生成的是i_call_ext_e指令。这两者有什么区别?i_call_f指令会直接跳到相应函数的入口(进程生成后,地址确定)并执行,而i_call_ext_e会根据模块导出函数列表中的地址跳转(详见[$OTP_SRC/erts/emulator/beam/beam_emu.c -->process_main)。这个地址在模块更新时会更新为当前版本代码导出函数的地址(详见[$OTP_SRC/erts/emulator/beam/beam_bif_load.c -->load_module_2] ,导出函数地址通过[$OTP_SRC/erts/emulator/beam/export.h]中Export结构的address定义)。从这里也可以看出来,除非有进程在引用旧版本的代码,模块更新后,其它进程是无法再通过任何方式引用到旧版本的代码(模块导出函数列表中的地址已更新)。
?