第 3 章 系統初始化

目录

3.1. 啓動過程概述
3.1.1. 第一階段:UEFI
3.1.2. 第二階段:引載加載程序
3.1.3. 第三階段:迷你 Debian 系統
3.1.4. 第四階段:常規 Debian 系統
3.2. Systemd 初始化
3.2.1. 主機名
3.2.2. 檔案系統
3.2.3. 網路介面初始化
3.3. 核心訊息
3.4. 系統訊息
3.5. 系統管理
3.6. 其它系統監控
3.7. 定製 systemd
3.7.1. 套接字啟用
3.8. udev 系統
3.8.1. 核心模組初始化

作爲系統管理員,粗略地瞭解 Debian 系統的啓動和配置方式是明智的。儘管準確的細節在安裝的軟件包及對應的文檔中,但這些知識對我們大多數人來說都是必須掌握的。

下面是 Debian 系統初始化的要點概述。由於 Debian 系統在不斷髮展,您應該參考最新的文件。

計算機系統從上電事件到能爲用戶提供完整的操作系統(OS)功能爲止,需要經歷幾個階段的啓動過程

爲簡便起見,筆者將討論範圍限定在具有默認安裝的典型 PC 平臺上。

典型的啓動過程像是一個四級的火箭。每一級火箭將系統控制權交給下一級。

當然,這些階段可以有不同的配置。比如,你編譯了自己的內核,則可能會跳過迷你 Debian 系統的步驟。因此,在讀者親自確認之前,請勿假定自己系統的情況也是如此。

Unified Extensible Firmware Interface (UEFI) 統一可擴充套件韌體介面 定義了啟動管理器作為 UEFI 規範的一部分。當一個計算機開啟電源,啟動管理器是啟動流程的第一階段,它檢查啟動配置並基於啟動配置的設定,執行特定的作業系統引導載入程式或作業系統核心(通常是引導載入程式)。啟動配置透過變數儲存在 NVRAM,變數包括指示作業系統引導載入程式或作業系統核心的檔案系統路徑的變數。 EFI system partition (ESP) EFI 系統分割槽 是一個數據儲存裝置分割槽,在計算機裡用來遵照 UEFI 規範。當計算機開啟電源時,由 UEFI 韌體來訪問,它儲存了 UEFI 應用程式和這些應用程式執行所需要的檔案,包括作業系統的引導載入程式。(在老的 PC 系統,存放在 MBR 裡的 BIOS 可以用來代替。)

引導載入程式是啟動過程的第二階段,由 UEFI 啟動。引導載入程式將系統核心映像和 initrd 映像載入到記憶體並將控制權交給它們。initrd 映像是根檔案系統映像,其支援程度依賴於所使用的引導載入程式。

Debian 系統通常使用 Linux 核心作為預設的系統核心。當前的 5.x Linux 核心的 initrd 映像在技術上是 initramfs(初始 RAM 檔案系統)映像。

有許多引導載入程式和配置選項存在。


[警告] 警告

假如沒有從 grub-rescue-pc 軟體包中的映像製作出來的可引導修復盤(隨身碟、CD 或軟盤),請勿玩弄引導載入程式。即使硬碟上沒有可正常工作的引導載入程式,可引導修復盤也能引導你的系統。

GRUB 2 的選單配置檔案位於 /boot/grub/grub.cfg,它的選單條目的關鍵部分看起來像:

menuentry 'Debian GNU/Linux' ... {
        load_video
        insmod gzio
        insmod part_gpt
        insmod ext2
        search --no-floppy --fs-uuid --set=root fe3e1db5-6454-46d6-a14c-071208ebe4b1
        echo    'Loading Linux 5.10.0-6-amd64 ...'
        linux   /boot/vmlinuz-5.10.0-6-amd64 root=UUID=fe3e1db5-6454-46d6-a14c-071208ebe4b1 ro quiet
        echo    'Loading initial ramdisk ...'
        initrd  /boot/initrd.img-5.10.0-6-amd64
}

對於這部分的 /boot/grub/grub.cfg,這個選單條目的意義如下。


[提示] 提示

透過設定在 /etc/default/grubGRUB_BACKGROUND 變數指向到影象檔案,或者把影象檔案本身放入 /boot/grub/,你能夠定製 GRUB 的啟動影象。

參見 “info grub” 及 grub-install(8)。

迷你 Debian 系統是啓動流程的第三階段,由引導加載程序啓動。它會在內存中運行系統內核和根文件系統。這是啓動流程的一個可選準備階段。

[注意] 注意

“迷你 Debian 系統”是筆者自創的術語,用於在本文檔中描述啓動流程的第三個階段。這個系統通常被稱爲 initrd 或 initramfs 系統。內存中類似的系統在 Debian 安裝程序中使用。

/init 程式是記憶體中的根檔案系統上執行的第一個程式。這個程式在使用者空間把核心初始化,並把控制權交給下一階段。迷你 Debian 系統能夠在主引導流程之前新增核心模組或以加密形式掛載根檔案系統,使引導流程更加靈活。

  • 如果 initramfs 是由 initramfs-tools 建立,則"/init" 程式是一個 shell 指令碼程式。

    • 通過給內核添加 “break=init" 等啓動參數,你可以中斷這部分啓動流程以獲取 root shell。更多中斷條件請參見 ”/init“ 腳本。這個 shell 環境已足夠成熟,你可通過它很好地檢查機器的硬件。

    • 迷你 Debian 系統中可用的命令是精簡過的,且主要由一個稱爲 busybox(1) 的 GNU 工具提供。

  • 如果 initramfs 是由 dracut 建立,則 "/init" 程式是一個二進位制 systemd 程式。

    • 迷你 Debian 系統中可用的命令是一個精簡過的 systemd(1) 環境。

[小心] 小心

當在一個只讀的根文件系統上時,使用 mount 命令需要添加 -n 選項。

常規 Debian 系統是啓動流程的第四階段,由迷你 Debian 系統啓動。迷你 Debian 系統的內核在此環境下繼續運行。根文件系統將由內存切換到實際的硬盤文件系統上。

init 程序是系統執行的第一個程序(PID=1),它啓動其它各種程序以完成主引導流程。init 程序的默認路徑是 ”/sbin/init“,但可通過內核啓動參數修改,例如 ”init=/path/to/init_program"。

在 Debian 8 jessie(2015 年釋出)版本後,"/sbin/init" 是一個到 "/lib/systemd/systemd" 的符號連結。

[提示] 提示

你的系統中實際使用的 init 命令可以使用 “ps --pid 1 -f” 命令確認。


[提示] 提示

有關啟動流程加速的最新資訊,請參見 Debian 維基:啟動流程加速詞條。

本節描述系統是怎樣透過 PID=1systemd(1) 程式來啟動(即初始化程序)。

systemd 初始化程序基於單元配置檔案 (參見 systemd.unit(5)) 來並行派生程序,這些單元配置檔案使用宣告樣式來書寫,代替之前的類 SysV 的過程樣式。這些單元配置檔案從下面的一系列路徑來載入 (參見 systemd-system.conf(5)) :

派生的程序被放在一個單獨的 Linux control groups,在單元后命名,它們屬於一個私有的 systemd 層級結構(參見 cgroups第 4.7.4 节 “Linux 安全特性”)。

單元配置檔案從下列一系列路徑中載入(參見 systemd-system.conf(5)):

  • "/lib/systemd/system": OS 預設配置檔案

  • "/etc/systemd/system": 系統管理員的配置檔案,它將忽略作業系統預設的配置檔案

  • "/run/systemd/system": 執行時產生的配置檔案,它將忽略安裝的配置檔案

他們的相互依賴關係透過"Wants=", "Requires=", "Before=", "After=", … 等指示來配置,(參見 systemd.unit(5) 裡的 "MAPPING OF UNIT PROPERTIES TO THEIR INVERSES")。 資源控制也是被定義 (參見 systemd.resource-control(5)).

根據單元配置檔案的字尾來區分它們的型別:

  • *.service 描述由 systemd 控制和監管的程序.參見 systemd.service(5).

  • *.device 描述在 sysfs(5) 裡面作為 udev(7) 裝置樹展示的裝置。參見 systemd.device(5).

  • *.mount 描述由 systemd 控制和監管的檔案系統掛載點。參見 systemd.mount(5).

  • *.automount 描述由 systemd 控制和監管的檔案系統自動掛載點。參見 systemd.automount(5).

  • *.swap 描述由 systemd 控制和監管的 swap 檔案或裝置。參見 systemd.swap(5).

  • *.path 描述被 systemd 監控的路徑,用於基於路徑的活動。參見 systemd.path(5).

  • *.socket 描述被 systemd 控制和監管的套接字,用於基於套接字的活動。參見 systemd.socket(5).

  • *.timer 描述被 systemd 控制和監管的計時器,用於基於時間的活動。參見 systemd.timer(5).

  • *.slice 管理 cgroups(7) 的資源。參見 systemd.slice(5).

  • *.scope 使用 systemd 的匯流排介面來程式化的建立,用以管理一系列系統程序。 參見 systemd.scope(5).

  • *.target 把其它單元配置檔案分組,在啟動的時候,來建立同步點。參見systemd.target(5).

系統啟動時(即,init),systemd 程序會嘗試啟動"/lib/systemd/system/default.target(通常是到"graphical.target"的符號連結)。首先,一些特殊的 target 單元(參見 systemd.special(7)),比如 "local-fs.target"、"swap.target"和"cryptsetup.target"會被引入以掛載檔案系統。之後,其它 target 單元也會根據單元依賴關係而被引入。詳細情況,請閱讀 bootup(7)。

systemd 提供向後相容的功能。在 "/etc/init.d/rc[0123456S].d/[KS]name" 裡面的 SysV 風格的啟動指令碼仍然會被分析;telinit(8) 會被轉換為 systemd 的單元活動請求。

[小心] 小心

模擬的執行級別 2 到 4 全部被符號連結到了相同的“multi-user.target”。

硬碟和網路檔案系統的掛載選項可以在 "/etc/fstab" 中設定,參見 fstab(5) 和 第 9.6.7 节 “通過掛載選項優化檔案系統”

加密檔案系統的配置設定在“/etc/crypttab”中。參見 crypttab(5)

軟 RAID 的配置 mdadm(8) 設定在 "/etc/mdadm/mdadm.conf". 參見 mdadm.conf(5).

[警告] 警告

每次啟動的時候,在掛載了所有檔案系統以後,"/tmp", "/var/lock", 和 "/var/run" 中的臨時檔案會被清空。

在控制檯上顯示的核心錯誤資訊,能夠透過設定他們的閾值水平來配置。

# dmesg -n3

systemd 下, 核心和系統的資訊都透過日誌服務 systemd-journald.service (又名 journald)來記錄,放在"/var/log/journal"下的不變的二進位制資料,或放在"/run/log/journal/"下的變化的二進位制資料.這些二進位制日誌資料,可以透過 journalctl(1) 命令來訪問。例如,你可以顯示從最後一次啟動以來的日誌,按如下所示:

$ journalctl -b

systemd 下,系統日誌工具 rsyslogd(8) 可以被解除安裝。如果安裝了它,它會改變它的行為來讀取易失性二進位制日誌資料(代替在 systemd 之前預設的 "/dev/log")並建立傳統的永久性 ASCII 系統日誌資料。"/etc/default/rsyslog" 和 "/etc/rsyslog.conf" 能夠自定義日誌檔案和螢幕顯示。參見 rsyslogd(8) 和 rsyslog.conf(5),也可以參見第 9.3.2 节 “日誌分析”

systemd 不僅僅提供系統初始化,還用 systemctl(1) 命令提供通用的系統管理操作。

表 3.6. 典型的 systemctl 命令片段列表

操作 命令片段
列出所有 target 單元配置 "systemctl list-units --type=target"
列出所有 service 單元配置 "systemctl list-units --type=service"
列出所有單元配置型別 "systemctl list-units --type=help"
列出記憶體中所有 socket 單元 "systemctl list-sockets"
列出記憶體中所有 timer 單元 "systemctl list-timers"
啟動 "$unit" "systemctl start $unit"
停止 "$unit" "systemctl stop $unit"
重新載入服務相關的配置 "systemctl reload $unit"
停止和啟動所有 "$unit" "systemctl restart $unit"
啟動 "$unit" 並停止所有其它的 "systemctl isolate $unit"
轉換到 "圖形" (圖形介面系統) "systemctl isolate graphical"
轉換到 "多使用者" (命令列系統) "systemctl isolate multi-user"
轉換到 "應急模式" (單使用者命令列系統) "systemctl isolate rescue"
向"$unit"傳送殺死訊號 "systemctl kill $unit"
檢查"$unit"服務是否是活動的 "systemctl is-active $unit"
檢查"$unit"服務是否是失敗的 "systemctl is-failed $unit"
檢查"$unit|$PID|device"的狀態 "systemctl status $unit|$PID|$device"
顯示"$unit|$job"的屬性 "systemctl show $unit|$job"
重設失敗的"$unit" "systemctl reset-failed $unit"
列出所有單元服務的依賴性 "systemctl list-dependencies --all"
列出安裝在系統上的單元檔案 "systemctl list-unit-files"
啟用 "$unit" (增加符號連結) "systemctl enable $unit"
禁用 "$unit" (刪除符號連結) "systemctl disable $unit"
取消遮掩 "$unit" (刪除到 "/dev/null" 的符號連結) "systemctl unmask $unit"
遮掩 "$unit" (增加到 "/dev/null" 的符號連結) "systemctl mask $unit"
獲取預設的 target 設定 "systemctl get-default"
設定預設 target 為"graphical" (圖形系統) "systemctl set-default graphical"
設定預設的 target 為"multi-user" (命令列系統) "systemctl set-default multi-user"
顯示工作環境變數 "systemctl show-environment"
設定環境變數 "variable" 的值為 "value" "systemctl set-environment variable=value"
取消環境變數 "variable" 的設定 "systemctl unset-environment variable"
重新載入所有單元檔案和後臺守護程序(daemon) "systemctl daemon-reload"
關閉系統 "systemctl poweroff"
關閉和重啟系統 "systemctl reboot"
掛起系統 "systemctl suspend"
休眠系統 "systemctl hibernate"

這裡, 上面例子中的"$unit",可以是一個單元名(字尾.service.target 是可選的),或者,在很多情況下,也可以是匹配的多個單元 (shell 式樣的全域性萬用字元"*", "?", "[]",透過使用 fnmatch(3) ,來匹配目前在記憶體中的所有單元的基本名稱).

上面列子的系統狀態改變命令,通常是透過"sudo"來處理,用以獲得需要的系統管理許可權。

"systemctl status $unit|$PID|$device" 的輸出使用有顏色的點("●")來概述單元狀態,讓人看一眼就知道。

  • 白色的 "●" 表示一個 "不活動"或"變為不活動中"的狀態。

  • 紅色的 "●"表示“失敗”或者“錯誤”狀態。

  • 綠色"●"表示“活動”、“重新載入中”或“啟用中”狀態。

這裡是 systemd 下其它零星的監控命令列表。請閱讀包括 cgroups(7) 在內的相關的 man 手冊頁。


使用預設安裝,透過 systemd 啟動的過程中,在 network.target 啟動後,很多網路服務 (參見 第 6 章 網路應用)作為後臺守護程序(daemon)啟動。 "sshd" 也不列外。讓我們修改為按需啟動"sshd" 作為一個定製化的例子。

首先,禁用系統安裝的服務單元。

 $ sudo systemctl stop sshd.service
 $ sudo systemctl mask sshd.service

傳統 Unix 服務的按需套接字啟用(on-demand socket activation)系統由 inetd (或 xinetd)超級服務來提供。在 systemd 下, 相同功能能夠透過增加*.socket*.service 單元配置檔案來啟用。

sshd.socket 用來定義一個監聽的套接字

[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target

sshd@.service 作為 sshd.socket 匹配的服務檔案

[Unit]
Description=SSH Per-Connection Server

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket

然後重新載入。

 $ sudo systemctl daemon-reload

從 Linux 核心 2.6 版開始,udev 系統 提供了自動硬體發現和初始化機制。(參見 udev(7)).在核心發現每個裝置的基礎上,udev 系統使用從 sysfs 檔案系統 (參見 第 1.2.12 节 “procfs 和 sysfs”)的資訊啟動一個使用者程序,使用 modprobe(8) 程式 (參見 第 3.8.1 节 “核心模組初始化”)載入支援它所要求的核心模組, 建立相應的裝置節點。

[提示] 提示

如果由於某些理由,"/lib/modules/kernel-version/modules.dep"沒有被 depmod(8) 正常生成,模組可能不會被 udev 系統按期望的方式載入。執行"depmod -a" 來修復它。

"/etc/fstab"裡面的掛載規則,裝置節點不必需是靜態的。你能夠使用 UUID 來掛載裝置,來代替"/dev/sda"之類的裝置名. 參見 第 9.6.3 节 “使用 UUID 訪問分割槽”.

由於 udev 系統是一個正在變化的事物,我在其它文件進行了詳細描述,在這裡只提供了最少的資訊。

通過 modprobe(8) 程式新增和刪除核心模組,使我們能夠從使用者程序來配置正在執行的 Linux 核心。udev 系統(參見 第 3.8 节 “udev 系統”)自動化它的呼叫來幫助核心模組初始化。

下面的非硬體模組和特殊的硬體驅動模組,需要被預先載入,把它們在"/etc/modules"檔案裡列出 (參見 modules(5)).

modprobe(8) 程式的配置檔案是按 modprobe.conf(5)的說明放在"/etc/modprobes.d/" 目錄下,(如果你想避免自動載入某些核心模組,考慮把它們作為黑名單放在"/etc/modprobes.d/blacklist" 檔案裡.)

"/lib/modules/version/modules.dep" 檔案由 depmod(8) 程式生成,它描述了 modprobe(8) 程式使用的模組依賴性.

[注意] 注意

如果你在啟動時出現模組載入問題,或者 modprobe(8)時出現模組載入問題, "depmod -a" 可以通過重構"modules.dep"來解決這些問題。

modinfo(8) 程式顯示 Linux 核心模組資訊。

lsmod(8) 程式以好看的格式展示"/proc/modules"的內容,顯示當前核心載入了哪些模組。

[提示] 提示

你能夠精確識別你係統上的硬體。 參見第 9.5.3 节 “硬體識別”.

你可以在啟動時配置硬體來啟用期望的硬體特徵。參見 第 9.5.4 节 “硬體配置”.

你可以重新編譯核心來增加你的特殊裝置的支援。參見 第 9.10 节 “核心”.