章節概覽#

當 kernel 啟動 init(第一個 user-space process)後,系統才真正開始建構完整的運作環境。User space 的啟動順序大致如下:

  1. init 啟動
  2. 基礎低階服務(如 udevd、syslogd)
  3. 網路設定
  4. 中高階服務(cron、列印等)
  5. 登入提示、GUI、高階應用程式(如 web server)

User space 比 kernel 更加模組化且容易自訂,理解和修改 user-space 啟動不需要低階程式設計知識。


6.1 init 簡介#

init 是一個普通的 user-space 程式,位於 /sbin,主要功能是啟動和停止系統上的關鍵服務 process。

目前所有主流 Linux 發行版的標準 init 實作都是 systemd。歷史上還有其他版本:

  • System V init(Sys V,傳統的順序式 init):RHEL 7.0 之前、Debian 8 之前使用
  • Upstart:Ubuntu 15.04 之前使用
  • runit:輕量系統上較常見

System V init 的限制#

傳統 System V init 依序執行一系列 script,每個 script 啟動一個服務。這種方式有幾個問題:

  • 效能問題:開機序列的各部分無法同時執行
  • 服務管理困難:需要手動追蹤 daemon 的 PID
  • 啟動 script 充滿樣板程式碼(boilerplate code)
  • 缺乏 on-demand 服務的概念,大多數服務都在開機時啟動

6.2 辨識你的 init#

判斷系統使用哪種 init 的方法:

  • 若系統有 /usr/lib/systemd/etc/systemd 目錄 → systemd
  • 若有 /etc/init 目錄且含多個 .conf 檔案 → 可能是 Upstart
  • 若有 /etc/inittab 檔案 → 可能是 System V init

6.3 systemd#

systemd 是目前最主流的 init 實作。除了處理開機流程,它還整合了許多傳統 Unix 服務的功能(如 cron、inetd)。systemd 的核心優勢在於進階的服務管理能力:能追蹤啟動後的 daemon、將多個 process 分組管理。

systemd 是目標導向(goal-oriented)的:你定義一個目標,稱為 unit,systemd 會自動解析其相依性並啟動所需的一切。

6.3.1 Unit 與 Unit 類型#

每種功能稱為一個 unit type,每個具體功能稱為一個 unit。最重要的 unit type:

  • Service units:控制系統上的 daemon
  • Target units:將其他 unit 分組管理
  • Socket units:代表網路連線請求的監聽位置
  • Mount units:代表檔案系統的掛載

完整的 unit type 列表可在 systemd(1) manual page 中找到。

6.3.2 開機與 Unit 相依性圖#

開機時,systemd 啟動一個預設的 unit,通常是 default.target,它匯集了一組 service 和 mount unit 作為相依性。這些相依性構成一個(graph),而非簡單的樹狀結構,因為後期的 unit 可能依賴多個前期 unit。

可以使用 systemd-analyze dot 指令產生相依性圖。

Figure 6-1: Unit dependency graph

在大多數系統上,default.target 是指向另一個高階 target unit(如啟動 GUI 的 target)的符號連結。

6.3.3 systemd 設定#

systemd 的設定檔分散在多個目錄:

  • System unit directory(全域設定):/lib/systemd/system/usr/lib/systemd/system
  • System configuration directory(本地設定):/etc/systemd/system

避免修改 system unit directory,因為發行版會維護它。本地修改應放在 /etc/systemd/system

查看設定搜尋路徑:

systemctl -p UnitPath show

查看系統 unit 和設定目錄:

pkg-config systemd --variable=systemdsystemunitdir
pkg-config systemd --variable=systemdsystemconfdir

Unit 檔案格式#

Unit 檔案格式源自 XDG Desktop Entry 規範,使用方括號的 section 名稱,以及 key=value 的設定方式。例如 dbus-daemon.service

[Unit]
Description=D-Bus System Message Bus
Documentation=man:dbus-daemon(1)
Requires=dbus.socket
RefuseManualStart=yes

[Service]
ExecStart=/usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
ExecReload=/usr/bin/dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig

Variables 與 Specifiers#

  • Variables:以 $ 開頭,如 $OPTIONS$MAINPID。可來自 EnvironmentFile 設定或 systemd 內部追蹤
  • Specifiers:以 % 開頭,如 %n(unit 名稱)、%H(hostname)。可用 @ 符號建立 unit 的多個實例(instance),例如 getty@tty1getty@tty2

6.3.4 systemd 操作#

透過 systemctl 指令與 systemd 互動:

# 列出所有啟用的 unit
systemctl list-units

# 查看特定 unit 的狀態
systemctl status sshd.service

# 查看 unit 的日誌
journalctl --unit=unit_name

啟動、停止與重新載入#

systemctl start unit
systemctl stop unit
systemctl restart unit
systemctl reload unit        # 僅重新載入該 unit 的設定
systemctl daemon-reload      # 重新載入所有 unit 設定

查看目前的 jobs(unit 狀態變更請求):

systemctl list-jobs

新增與移除 Unit#

新增 unit 的步驟:

  1. /etc/systemd/system 建立 unit 檔案
  2. 若有 [Install] section,需先 enable:systemctl enable unit
  3. 啟動 unit:systemctl start unit

移除 unit 的步驟:

  1. 停止 unit:systemctl stop unit
  2. 若有 [Install] section:systemctl disable unit
  3. 刪除 unit 檔案

enable 一個 unit 並不會啟動它,它只是建立相依性的符號連結。

6.3.5 systemd Process 追蹤與同步#

systemd 使用 cgroups(Linux kernel 功能)來追蹤每個服務的所有 process,無論服務如何 fork 或 daemonize。

透過 unit 檔案中的 Type 選項指定啟動行為:

  • Type=simple:process 不 fork,直接作為主服務 process
  • Type=forking:服務會 fork,原始 process 結束後視為就緒
  • Type=notify:服務就緒時透過特殊函式呼叫通知 systemd
  • Type=dbus:服務就緒時在 D-Bus 上註冊
  • Type=oneshot:process 執行完畢後就結束,不留常駐 process
  • Type=idle:類似 simple,但等所有活躍 jobs 完成後才啟動

6.3.6 systemd 相依性#

systemd 提供多種相依性類型以兼顧彈性與容錯:

關鍵字說明
Requires嚴格相依。相依的 unit 失敗時,本 unit 也會被停用
Wants僅用於啟動。相依的 unit 失敗不影響本 unit
Requisite相依的 unit 必須已經處於啟用狀態
Conflicts負向相依。啟動本 unit 時會停用衝突的 unit

systemd.service(5) manual page 建議盡量使用 Wants 來指定相依性,因為它不會將失敗傳播到其他 unit,使系統更加穩健。

查看 unit 的相依性:

systemctl show -p type unit

排序(Ordering)#

RequiresWants 本身不指定啟動順序,相依的 unit 會同時啟動以加速開機。若需要指定順序:

  • Before:本 unit 在指定 unit 之前啟動
  • After:本 unit 在指定 unit 之後啟動

預設與隱含相依性#

systemd 會自動為 Wants 相依性加上 After 修飾符,這稱為 default dependency。可在 unit 設定中加入 DefaultDependencies=no 來停用。

條件式相依性(Conditional Dependencies)#

可使用條件參數來根據系統狀態決定是否啟動 unit:

  • ConditionPathExists=p:路徑 p 存在時才啟動
  • ConditionPathIsDirectory=p:p 是目錄時才啟動
  • ConditionFileNotEmpty=p:p 是非空檔案時才啟動

[Install] Section 與 Enable#

[Install] section 允許在相依性的 unit 中反向定義相依關係:

  • WantedBy:enable 時在目標 unit 的 .wants 目錄建立符號連結
  • RequiredBy:enable 時在目標 unit 的 .requires 目錄建立符號連結
# enable 會建立符號連結
systemctl enable test1.target

# disable 會移除符號連結
systemctl disable test1.target

6.3.7 systemd On-Demand 與資源平行化啟動#

systemd 的重要功能之一是延遲啟動(on-demand startup):

  1. 建立一個 service unit(Unit A)和一個 resource unit(Unit R,如 socket unit)
  2. 啟動 Unit R 後,systemd 監控該資源
  3. 當有存取資源的請求時,systemd 緩衝輸入並啟動 Unit A
  4. Unit A 就緒後接管資源,正常運作

Socket Unit 與 Service 範例#

以 echo service 為例,建立 echo.socket

[Unit]
Description=echo socket

[Socket]
ListenStream=22222
Accept=true

對應的 echo@.service(透過命名慣例自動關聯):

[Unit]
Description=echo service

[Service]
ExecStart=/bin/cat
StandardInput=socket

以 Auxiliary Unit 優化開機#

Resource unit(如 socket unit)也可用於開機優化:systemd 先啟動 resource unit 來提供介面,讓依賴該資源的 unit 能立即開始啟動,不必等待實際服務就緒。這使得原本必須循序啟動的服務可以平行化。

Figure 6-2: Sequential boot timeline with a resource dependency

Figure 6-3: systemd boot timeline with a resource unit

6.3.8 systemd 輔助元件#

systemd 包含多個輔助系統服務,執行檔都以 systemd- 為前綴:

  • udevdsystemd-udevd):裝置管理
  • journald:日誌服務,處理多種日誌機制(包含傳統 syslog)
  • resolved:DNS 名稱解析快取

6.4 System V Runlevels#

在傳統 System V init 中,系統的運作狀態稱為 runlevel,以 0 到 6 的數字表示:

  • Runlevel 0:關機(halt)
  • Runlevel 1:單人模式(single-user mode)
  • Runlevel 2-4:文字主控台
  • Runlevel 5:GUI 登入
  • Runlevel 6:重新開機(reboot)

查看目前 runlevel:

who -r

Runlevel 的概念正在逐漸淘汰。即使 systemd 支援 runlevel,它更偏好使用 target unit 作為系統的最終狀態。Runlevel 主要為了相容 System V init script 而存在。


6.5 System V init#

System V init 的核心思想是透過精心建構的啟動順序支援不同 runlevel 之間的切換。

6.5.1 啟動指令序列#

System V init 的設定檔為 /etc/inittab,其中每行格式為四個欄位(以冒號分隔):

  1. 唯一識別碼
  2. 適用的 runlevel
  3. init 應採取的動作(如 initdefaultwaitrespawnctrlaltdelsysinit
  4. 要執行的指令(可選)

inittab 中的關鍵行會觸發 rc 指令,例如:

l5:5:wait:/etc/rc.d/rc 5

rc 5 會依序執行 /etc/rc5.d 目錄中以 S 開頭的 script(帶 start 參數),數字決定執行順序(00-99)。

rc*.d 目錄中的檔案其實是指向 /etc/init.d 目錄中 script 的符號連結,這種結構稱為 link farm。所有 runlevel 共用同一組啟動 script,只是透過不同的符號連結來控制哪些服務要啟動。

  • S 開頭:啟動模式(start)
  • K 開頭:停止模式(kill/stop)

修改開機順序#

最常見的做法是在 link 名稱前加底線來停用服務:

mv S99httpd _S99httpd

6.5.3 run-parts#

run-parts 是 System V init 用來執行 init.d script 的工具,它會依可預測的順序執行指定目錄中的所有可執行程式。不同發行版的 run-parts 功能各有差異。

6.5.4 System V init 控制#

使用 telinit 指令控制 System V init:

telinit 3    # 切換到 runlevel 3
telinit q    # 重新讀取 inittab
telinit s    # 切換到單人模式

切換 runlevel 時,init 會嘗試終止不在新 runlevel inittab 設定中的 process,操作時需謹慎。

6.5.5 systemd 的 System V 相容模式#

systemd 能追蹤 System V init script 啟動的服務:

  1. 啟動 runlevel<N>.target
  2. 辨識 /etc/rc<N>.d 中的符號連結對應的 /etc/init.d script
  3. 將 script 與 service unit 關聯(如 /etc/init.d/foo 對應 foo.service
  4. 啟動 service unit 並執行 script

這讓你可以用 systemctl 來管理這些舊式服務,但 init script 仍然是循序執行的。


6.6 關閉系統#

使用 shutdown 指令來關機或重新開機:

shutdown -h now     # 立即關機(halt)
shutdown -r now     # 立即重新開機(reboot)
shutdown -r +10     # 10 分鐘後重新開機

關機流程:

  1. init 要求所有 process 正常結束
  2. 若 process 未回應,先送 TERM signal
  3. 仍未回應則送 KILL signal
  4. 鎖定系統檔案
  5. 卸載 root 以外的所有檔案系統
  6. 將 root 檔案系統重新掛載為唯讀
  7. sync 寫出所有緩衝資料
  8. 透過 reboot(2) system call 通知 kernel 重新開機或停止

關機過程中應避免重設或直接斷電。若必須緊急關機,可使用 shutdown -f(force)選項。


6.7 Initial RAM Filesystem#

initramfs(initial RAM filesystem)是在正常 user mode 啟動之前的一個小型 user-space 環境。它存在的原因:

  • Linux kernel 不直接與 PC BIOS/EFI 溝通來讀取磁碟
  • Kernel 需要儲存裝置的驅動程式才能掛載 root filesystem
  • 許多儲存控制器驅動以 loadable modules 形式提供
  • 若沒有檔案系統,就無法載入模組檔案

運作流程#

  1. Boot loader 將 initramfs archive 載入記憶體
  2. Kernel 將其解壓到臨時 RAM filesystem,掛載在 /
  3. 執行 initramfs 上的 init
  4. initramfs 中的工具載入必要的驅動模組
  5. 掛載真正的 root filesystem
  6. 啟動真正的 init

如果你的 kernel 已包含掛載 root filesystem 所需的所有驅動,可以在 boot loader 設定中省略 initramfs,稍微縮短開機時間。

檢查 initramfs 內容的工具:

  • mkinitramfs 建立的 archive 可用 unmkinitramfs 解包
  • 較舊的系統可能使用壓縮的 cpio archive

initramfs 末尾有一個重要的 pivot 過程,負責移除臨時檔案系統的內容並永久切換到真正的 root。

initramfs 指的是使用 cpio archive 的實作。較舊的版本稱為 initrd(initial RAM disk),使用磁碟映像檔。雖然 initrd 已不再使用,但這個術語仍常被用來指 cpio-based 的 initial RAM filesystem。


6.8 Emergency Booting 與 Single-User Mode#

當系統出問題時,有兩個主要的救援選項:

Live Image#

使用發行版的 live image 或專用的救援映像檔(如 SystemRescueCD)開機。常見的修復任務:

  • 系統當機後檢查檔案系統
  • 重設遺忘的密碼
  • 修復關鍵設定檔(如 /etc/fstab/etc/passwd
  • 從備份還原

Single-User Mode#

系統快速開機到 root shell,跳過完整的服務啟動流程:

  • System V init 中對應 runlevel 1
  • systemd 中對應 rescue.target
  • 在 boot loader 加上 -s 參數進入

Single-user mode 的功能有限:網路幾乎不可用、沒有 GUI、終端機可能無法正常運作。因此 live image 通常是更好的選擇。


6.9 展望#

本章涵蓋了 kernel 到 user-space 啟動的完整流程,以及 systemd 如何管理服務。接下來將深入探討 user space 的兩個面向:系統設定檔(所有 Linux 程式都會用到的)以及 systemd 啟動的核心服務。