熟悉 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> 。