2019年3月24日 星期日

OpenWrt procd

OpenWrt 使用 procd 取代傳統 Linux 使用的 init 及 udev。

procd 原始碼除了套件 procd 外,還包括套件 procd-ujail、procd-seccomp、procd-nand、procd-nand-firstboot。procd 套件包括二進位程式 init、procd、askfirst、udevtrigger,shell 指令檔 reload_config 和 procd.sh,以及 hotplug json 檔 hotplug-preinit.json 和 hotplug.json。


在 OpenWrt 開機,Linux 最後會執行腳本 /etc/preinit註1 開始 userspace 的動作,但現在一開始就先插隊執行 /sbin/init,然後才執行原本的 /etc/preinit,最後交棒給 procd 繼續執行,變成:/sbin/init → /etc/preinit → /sbin/procd。

註: 在 OpenWrt 開機,Linux 最後會執行腳本 /etc/preinit (`try_to_run_init_process("/etc/preinit")`@init/main.c → do_execve()@fs/exec.c), 執行時會檢查檔案開頭決定何種執行檔,腳本需要以 #! 開頭 (load_script()@fs/binfmt_script.c),/etc/preinit 使用的直譯器是 busybox sh

init

一開始 PREINIT 沒設,執行 init 取代自己
[ -z "$PREINIT" ] && exec /sbin/init
  1. `ulog_open(ULOG_KMSG, LOG_DAEMON, "init")`: 設定 log 方式
  2. SIGTERM、SIGUSR1、SIGUSR2 的處置是 sa_shutdown
  3. `early()`:掛載檔案系統及設定環境變數
  4. `cmdline()`:讀取 Linux 指令行參數 init_debug 設定 debug 等級。
  5. `watchdog_init(1)`:初始化 watchdog,使用 /dev/watchdog
  6. fork() 執行 `/sbin/kmodloader /etc/modules-boot.d/` 載入一些 kernel 模組,並等候結束
  7. fork() 執行 `/sbin/procd -h /etc/hotplug-preinit.json` 處理事件
    • 載入韌體:使用 /sbin/hotplug-call
    • failsafe 按鍵:執行 /etc/rc.button/failsafe 產生 /tmp/failsafe_button
  8. fork() 執行 `PREINIT=1 /bin/sh /etc/preinit`,執行 /etc/preinit 原本該跑的腳本,結束時執行
    1. 如果 hotplug-preinit.json 還在跑,殺了他。
    2. 如果檔案 /tmp/sysupgrade 存在,就一直睡吧不往下執行了 (等候 reboot 嗎?) 
    3. 清掉環境變數 INITRAMFS、PREINIT
    4. 設環境變數 WDTFD 交出 watchdog file descriptor
    5. 交接除錯等級
    6. 改為繼續執行 /sbin/procd

procd

procd 其實可看成是兩個程式,有加 -h 參數是當作 hotplug daemon (hotplug_run()),否則
  1. 繼承除錯等級或重設,並設定 log 方式
  2. `setsid()`:建立新 session 並成為 process goup leader,且成為 process group 唯一的成員,使用 process ID 作為 session ID 及 process group ID,回傳 session ID。no controlling terminal。如果已經是 process group leader,則回傳 -1 且 errno 為 EPERM。
  3. `procd_signal()`:signal 的處置
    • SIGTERM、SIGINT、SIGUSR1、SIGUSR2:sa_shutdown
    • SIGSEGV、SIGBUS:sa_crash
    • SIGHUP、SIGKILL、SIGSTOP:sa_dummy
  4. 如果不是第 1 個 process,一秒後連結到 ubus,否則進入以下 state
  5. STATE_EARLY
    1. 設定 watchdog timeout
    2. hotplug(/etc/hotplug.json) 事件處理
    3. fork() 執行 `udevtrigger`,結束 ... 進入下個 state
  6. STATE_UBUS
    1. 重開 /dev/console stdin/stdout/stderr
    2. ubus timeout:ubus 連上後最後進入下個 state
    3. service_start_early()
  7. STATE_INIT
    1. LOG("- init -\n");
    2. `procd_inittab()`:讀取 inittab 建立 actions 列表 (id:run-level:action:process)
      • process 切出 argv
      • 除了 sysinit 及 shutdown 外,可以有多個
    3. `procd_inittab_run("respawn")`:
    4. `procd_inittab_run("askconsole")`
    5. `procd_inittab_run("askfirst")`
    6. `procd_inittab_run("sysinit")`:依序執行 /etc/rc.d/ 下 S 開始的腳本中的 boot() 部份
    7. `ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd")`:切換到 syslog
  8. STATE_RUNNING:沒做什麼事,只是 log 完成 init
  9. STATE_SHUTDOWN:shutdown 時進入,依序執行 /etc/rc.d/ 下 K 開始的腳本中的 shutdown() 部份
  10. STATE_HALT
sysinit 跟 shutdown 都是執行 /etc/rc.d/ 下的腳本,這些腳本通常是依所要執行的順序連結到 /etc/init.d/ 下的腳本,在 inittab 可能的內容如下:
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
在 procd 會用 glob() 取出所有 /etc/rc.d/S* 的檔案,全部加到 libubox runqueue 去執行。

askfirst

用在 inittab

udevtrigger

...

procd.sh

共用腳本函數,當 init 腳本使用 procd 方式時 (USE_PROCD=1) 使用。

OpenWrt 的 init 腳本通常透過 /etc/rc.common 執行,在開機時是執行 shell function  boot(),而 boot() 預設是執行 start()。在傳統方式,由 init 腳本提供 start() 功能,但當使用 procd 方式時則改提供 start_service()。
oldprocd
start()start_service() 至少要提供 json command 的部份
stop()已採用 procd_kill 停止程式,stop_service()
reload()如有 reload_service() 則執行,不然執行 start()
running()
trace()
使用 procd 方式的特別之處
  • 所有引數會打包成 json 格式,透過 ubus 送給 procd
    • command
    • respawn
    • ...
  • service_triggers() 可用 procd_add_reload_trigger() 登記哪些設定檔改變後執行 reload_config 需要重新執行。可用 procd_add_network_trigger() 登記網路改變作為重啟的 trigger

範例:package/network/services/dnsmasq

詳情見 OpenWrt package/base-files/files/etc/rc.common。
rc.common 會載入 /lib/functions.sh (一些共同的函數,包括 config_ 開頭的函數)、/lib/config/uci.sh (uci_ 開頭存取 uci 的函數)、及 /lib/functions/service.sh (使用 busybox start-stop-daemon 提供 service_ 開頭的函數)。

reload_config

configd 完成前的臨時替代方案,
檢查設定有改變則呼叫 `ubus call service event { "type": "config.change", "data": { "package": "設定檔名" }}`

開機時會產生設定檔的 MD5 checksum 存在記憶體,更改設定後執行 reload_config 會自動重啟相關的程式。

hotplug-preinit.json 及 hotplug.json。

...

延伸閱讀


/sbin/procd (不設 INITRAMFS,不設 PREINIT)
│ hotplug(/etc/hotplug.json)
├→ udevtrigger
│ubus_connect()...service_init, /etc/inittab
│? /sbin/ubus
├respawn→
├askconsole→
├askfirst→/sbin/askfirst
sysinit⇄用 runqueue 一次跑一個 /etc/rc.d/S* boot,pipe STDOUT/STDERR

hotplug_run() 每次事件發生時
exec→
load-firmware→

libubox uloop
libubox blob, blobmsg
json_script_init
ubus
service
ustream

延伸閱讀:http://data.pavlix.net/installfest/2014/openwrt-software.pdf

沒有留言:

張貼留言

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...