代码替换

在嵌入式实时系统中,我们希望在不停机的情况下进行代码升级。比如我们希望在不影响服务的情况下修复某台大型交换机中的软件错误。

在运营过程中进行代码替换是“软”实时控制系统的普遍需求,这些系统往往运营时间很长,代码体积也很大。而在特殊处理器上运行或烧录在ROM里的硬实时系统则往往没有这种需求。

代码替换实例

考察程序9.1。

我们首先编译并加载code_replace的代码。然后我们启动程序,并向创建出来的进程发送消息helloglobalprocess

程序9.1

  1. -module(code_replace).
  2. -export([test/0, loop/1]).
  3.  
  4. test() ->
  5. register(global, spawn(code_replace, loop, [0])).
  6.  
  7. loop(N) ->
  8. receive
  9. X ->
  10. io:format('N = ~w Vsn A received ~w~n', [N, X])
  11. end,
  12. code_replace:loop(N+1).

最后我们再次编辑程序,将版本号从A改为B,重新编译、加载程序,并向进程发送消息hello

会话结果如下:

  1. %%% start by compiling and loading the code
  2. %%% (this is done by c:c)
  3. > c:c(code_replace).
  4. ...
  5. > code_replace:test().
  6. true
  7. > global ! hello.
  8. N = 0 Vsn A received hello
  9. hello
  10. > global ! global.
  11. N = 1 Vsn A received global
  12. global
  13. > global ! process.
  14. N = 2 Vsn A received process
  15. %%% edit the file code_replace.erl
  16. %%% recompile and load
  17. > c:c(code_replace).
  18. ....
  19. > global ! hello.
  20. N = 3 Vsn B received hello

这里我们看到,在loop/1的执行过程中,虽然我们重新编译、加载了它的代码,但作为loop/1的参数的局部变量N的值仍被保留了下来。

注意服务器循环的代码是以如下形式编写的:

  1. -module(xyz).
  2.  
  3. loop(Arg1, ..., ArgN) ->
  4. receive
  5. ...
  6. end,
  7. xyz:loop(NewArg1, ..., NewArgN).

这与下面这样的写法有细微的差异:

  1. -module(xyz).
  2.  
  3. loop(Arg1, ..., ArgN) ->
  4. receive
  5. ...
  6. end,
  7. loop(NewArg1, ..., NewArgN).

第一种情况中调用xyz:loop(…)意味着总是使用模块xyz最新loop版本。第二种情况中(不显式指定模块名)则只调用当前执行模块中的loop版本。

显式使用模块限定名(module:func)使得module:func动态链接至运行时代码。对于使用完整模块限定名的调用,系统每次都会使用最新版本的可用代码进行函数求值。模块中本地函数的地址解析在编译期完成——它们是静态的,不能在运行时改变。

上述会话示例中c:c(File)编译并加载File中的代码。在第??节对此有详细讨论。