[elixir! #0082] application controller 应用是如何被载入和启动的

熟悉 erlang/elixir 的朋友们应该知道 application 的概念,它是一种特殊的结构,用于启动和停止一个应用。每当我们新建一个 erlang/elixir 项目,也同时新建了一个同名的 应用。在使用依赖库的时候,一般每个依赖库也是一个应用,会在我们运行项目时被载入和启动。

那么,erlang是如何管理这些应用的呢?这就是 :application_controller 发挥作用的地方了。所有的应用的载入、启动等状态的保存和变更,都要经过这个进程。

# 列出当前的全部应用状态
> :application_controller.info()

虚应用

不是所有的应用都会启动进程树,有些应用即使处于 :started 状态,也没有启动任何进程。姑且称这种应用为虚应用吧。

跟踪

利用我之前文章里提到的 bony_trance 库来跟踪一下 在load 和 start 一个应用时,:application_controller 进程都做了什么吧。

stop 应用

iex(3)> :application_controller.stop_application :play
#PID<0.44.0> RECEIVED                                                 +67.069019s
MESSAGE: {:"$gen_call", {#PID<0.199.0>, #Reference<0.2783639343.3287547907.121340>}, {:stop_application, :play}}
#PID<0.44.0> SENT TO: :code_server                                    +0.000047s
MESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, Logger.Translator}}
#PID<0.44.0> RECEIVED                                                 +0.004582s
MESSAGE: {:code_server, {:module, Logger.Translator}}
#PID<0.44.0> SENT TO: :code_server                                    +0.000033s
MESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, Logger.Utils}}
#PID<0.44.0> RECEIVED                                                 +0.002413s
MESSAGE: {:code_server, {:module, Logger.Utils}}
#PID<0.44.0> SENT TO: :code_server                                    +0.000012s
MESSAGE: {:code_call, #PID<0.44.0>, {:ensure_loaded, :calendar}}
#PID<0.44.0> RECEIVED                                                 +0.012388s
MESSAGE: {:code_server, {:module, :calendar}}

12:38:54.714 [info]  Application play exited: :stopped
:ok
#PID<0.44.0> SENT TO: Logger                                          +0.000015s
MESSAGE: {:notify, {:info, #PID<0.64.0>, {Logger, ["Application ", "play", " exited: " | ":stopped"], {{2021, 11, 28}, {12, 38, 54, 714}}, [erl_level: :notice, domain: [:otp], error_logger: %{report_cb: &:application_controller.format_log/1, tag: :info_report, type: :std_info}, file: "application_controller.erl", function: "info_exited/3", gl: #PID<0.64.0>, line: 2119, mfa: {:application_controller, :info_exited, 3}, module: :application_controller, pid: #PID<0.44.0>, report_cb: &:application_controller.format_log/2, time: 1638074334714339]}}}
#PID<0.44.0> SENT TO: #PID<0.199.0>                                   +0.000015s
MESSAGE: {#Reference<0.2783639343.3287547907.121340>, :ok}

首先向 code_server 确认一些必要的用于打印log的模块是已载入的, 这一步的结果会缓存,之后不必重复询问。如果该应用有 application 进程,则会向其发送 :stop 消息。最后打印出应用已停止的log。

start 及 unload 并不需要太复杂的消息交互,与 stop 类似。

load 应用

iex(9)> :application_controller.load_application :play
#PID<0.44.0> RECEIVED                                                 +26.948851s
MESSAGE: {:"$gen_call", {#PID<0.199.0>, #Reference<0.2783639343.3287547907.121460>}, {:load_application, :play}}
#PID<0.44.0> SENT TO: :code_server                                    +0.000014s
MESSAGE: {:code_call, #PID<0.44.0>, :get_path}
#PID<0.44.0> RECEIVED                                                 +0.000210s
MESSAGE: {:code_server, [...]}
#PID<0.44.0> SENT TO: #PID<0.10.0>                                    +0.000043s
MESSAGE: {#PID<0.44.0>, {:list_dir, '.../play/_build/dev/lib/play/consolidated'}}
#PID<0.44.0> RECEIVED                                                 +0.008352s
MESSAGE: {#PID<0.10.0>, {:ok, ['Elixir.Inspect.beam', 'Elixir.IEx.Info.beam', 'Elixir.String.Chars.beam', 'Elixir.List.Chars.beam', 'Elixir.Collectable.beam', 'Elixir.Enumerable.beam']}}
#PID<0.44.0> SENT TO: #PID<0.10.0>                                    +0.000018s
MESSAGE: {#PID<0.44.0>, {:list_dir, '/Users/linjiezhang/Documents/play/_build/dev/lib/play/ebin'}}
#PID<0.44.0> RECEIVED                                                 +0.010195s
MESSAGE: {#PID<0.10.0>, {:ok, ['Elixir.MyTracer.beam', 'Elixir.Play.beam', 'play.app', 'Elixir.Demo.beam']}}
#PID<0.44.0> SENT TO: #PID<0.10.0>                                    +0.000026s
MESSAGE: {#PID<0.44.0>, {:get_file, '/Users/linjiezhang/Documents/play/_build/dev/lib/play/ebin/play.app'}}
#PID<0.44.0> RECEIVED                                                 +0.000519s
MESSAGE: {#PID<0.10.0>, {:ok, "{application,play,\n             [{applications,[kernel,stdlib,elixir,logger,syntax_tools,\n                             bony_trace]},\n              {description,\"play\"},\n              {modules,['Elixir.Demo','Elixir.MyTracer','Elixir.Play']},\n              {registered,[]},\n              {vsn,\"0.1.0\"}]}.\n", '/Users/linjiezhang/Documents/play/_build/dev/lib/play/ebin/play.app'}}
:ok
#PID<0.44.0> SENT TO: :init                                           +0.000149s
MESSAGE: {#PID<0.44.0>, {:get_argument, :play}}
#PID<0.44.0> RECEIVED                                                 +0.000102s
MESSAGE: {:init, :error}
#PID<0.44.0> SENT TO: #PID<0.199.0>                                   +0.000009s
MESSAGE: {#Reference<0.2783639343.3287547907.121460>, :ok}

在 load 应用时,AC 通过 code_server 获取了全部应用的文件路径,之后拿到了此应用的确切的模块以及 .app 文件。然后向 :init 进程请求此应用的参数。这里就不深究这些交互的具体作用了,之后的文章里可以详细解读一下 :init 进程的作用。它相当于 erlang 世界里的创世主,拥有 PID<0.0.0> 。

你可能感兴趣的:(elixirerlang)