按下電源鍵,隨著風扇轉動的聲音,顯示器上開啟的圖示亮起。之後,只需要靜靜等待幾秒鐘,登入介面顯示,輸入密碼,即可愉快的玩耍了。
這是我們大概每天都做的事情。那麼中間到底發生了什麼?
簡單地說,從BIOS或者UEFI開始讀取硬碟。接下來,進入bootloader(LILO或者GRUB),bootloader開始載入核心,核心初始化完畢後,緊接著進入使用者空間的初始化。
使用者空間的啟動的第一個程式即pid=1,就是從一個叫init的程式開始的,這也是本文的主角
- 1. Systemd簡介與使用
1. Systemd簡介與使用
1.1. 使用者空間的啟動順序
使用者的空間的大致啟動順序如下:
- init
- 基礎底層服務,如udevd(裝置管理器),syslogd(日誌管理)
- 網路配置
- 中高層服務,如cron(定時器)
- 登入提示符(getty)、GUI、mysql(如果設定開機啟動的話)
init是核心啟動的第一個使用者空間程式,主要負責啟動、終止系統中的基礎服務程式。
Linux下,init主要有三個實現版本:
- System V,傳統的init
- Upstart,Ubuntu後期針對sys-v的一個改進實現版本
- systemd,是一套中央化系統及設定管理程式(init),包括有守護程式、程式庫以及應用軟體,相容sys-v。現代大部分桌面版都使用此實現。也是本文主要介紹的一個…emmmm…框架。是的,systemd更像一個服務管理框架。
1.2. SystemV
先說說傳統的SystemV,他其實就是利用一系列指令碼來啟動服務,之後的事就撒手不管了。
SystemV init依賴一個特定的啟動順序每次只能啟動執行一個啟動任務。
這些都是通過一個核心配置檔案tab(/etc/init
)和一組啟動指令碼以及符號連結集執行的,本質上為系統提供了合理的啟動順序,
支援不同的執行級別。
他的好處是依賴關係簡單,任務之間涇渭分明的一個一個啟動,即使某個基礎服務出了錯也便於排查。但也正因為如此,他的啟動效能很不好。
服務無法並行啟動不說,而且只能按照預先規定的順序啟動服務。如果你安裝了新的硬體或者新服務,他不提供及時支援的標準方法。
圖1
我們把使用者空間init的服務分別叫做Job A
、Job B
…圖1可以看到,在SysV init之下,服務必須一個接一個的順序啟動,前面的服務初始化完畢,後面才可以開始。因此,啟動時間就是所有服務啟動時間之和。
他的改進版Upstart
在此基礎上就做了優化——互不相關的服務可以並行啟動,這樣啟動總時間就等於時間消耗最大的一組服務,而不是所有服務之和。systemd在並行啟動上採取了比Upstart
更加激進的方案
圖2
圖2是systemd的並行啟動方式,他讓配置所有的服務同時啟動。如果Job Aing
依賴Job B
怎麼辦呢?首先兩個Job
是同時啟動的,A如果先啟動,就向B傳送請求服務,B會先將請求快取起來,等到B初始化完畢之後,再處理快取的請求。
相比SysV init,這也帶來了不確定性,即你不知道此時到底哪些服務起了,哪些沒起,全依賴系統管理
1.2.1. 執行級別
執行級別的概念最早應該也是來自於SysV init.
簡單地說,執行級別定義了系統的特定狀態,這種狀態可以看成一系列服務狀態的集合。
不同的發行版有不同的執行級別,但比較公認的如下:
- 0,關機
- 1,單使用者模式(修復模式),如果你的系統涼涼了,這將是你的救命稻草
- 6,重啟
以我個人的deepin15.7
為例,如圖
其中runlevel2/3/4都屬於同一個執行等級(multi-user),而系統的預設的執行等級為5——graphical。我們平時所用的桌面環境就是這個等級了。其實,現代大部分採用systemd的發行版都和這個大同小異。
我們使用systemctl cat graphical.target
開啟graphical.target
檔案,可以看到下面內容:
[Unit]
Description=Graphical Interface
Documentation=man:systemd.special(7)
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target display-manager.service
AllowIsolate=yes
其中的Requires=multi-user.target
表示,如果想啟動graphical.target
(即執行等級5)就必須先啟動multi-user.target
(執行等級3).由此可見,在systemd中,執行等級5就是在等級3基礎上,同時啟動一個display-manager
服務。display-manage
顧名思義,肯定是和影像顯示有關的咯。
如果你對.target
檔案,和他的定義語法很迷惑,沒有關係,後面還會詳細解釋。我舉這個例子,只是想讓你瞭解systemd是相容systemV的執行等級概念的。所以,你關於SystemV的認識也是可以繼續沿用的
1.3. Systemd
在Linux中以d
結尾的,表示這是一個守護程式,systemd就是這個系統的守護程式
相比於之前的版本,systemd最關鍵的特性是:
- 延遲啟動某些服務和系統功能,等到需要他們的時候才開啟
- 完全並行啟動
systemd 架構圖
1.3.1. systemd啟動步驟
systemd的特性複雜,下面給出大致的啟動步驟,使我們有個總體觀:
- systemd載入配置資訊
- 判定啟動目標,一般是default.target
- 判定啟動目標的依賴關係
- 啟用依賴服務,啟動目標
- 響應系統訊息,啟用其他元件
1.3.2. 單元和單元型別
systemd不光負責處理程式和服務,同時還能掛載檔案系統、監控網路套接字等等。在systemd中
所有服務和功能都被抽象成一個個單元(Unit),根據功能不同,單元型別也不同。systemd正是通過配置這些單元
來開關、管理服務的。
1.3.2.1. 單元型別
比較常用的幾種:
- 服務單元,傳統的守護程式(XXX.service檔案表示)
- 掛載單元,控制檔案系統掛載(XXX.mount檔案表示)
- 目標單元,將服務單元、掛載單元等單元組織在一起的單元,一般對應Sys-V的執行等級(XXX.target檔案表示)
上面的尤其是服務單元我們會經常打交道,而且必要時也可以自定義服務單元等。比如我們的藍芽功能就抽象成
blueteeth.service
,管理磁碟的udev系統對應systemd-udevd.service
檔案。如果你安裝了mysql,
還可以找到一個mysql.service
檔案。
使用deepin15.7的過程中,遇到過一個bug,就是在系統長期休眠之後再重啟,藍芽模組莫名其妙的關閉了,進入[設定]皮膚也
無法找到藍芽配置選項了。這時執行systemctl restart blueteeth.service
重啟藍芽模組,大概率就會修復了
除了以上幾種,還有其他型別,比如
socket單元(.socket
)、系統裝置單元(.device
)、交換單元(.swap
)、路徑單元(.path
)、定時單元(.time
),
不一而足
1.3.3. systemd相關指令
1.3.3.1. 電源管理
主要涉及開關、系統重啟等,如果你是當前唯一使用者的話則不需要提權,否則需要root密碼
systemctl reboot #重啟
systemctl poweroff #關機
systemctl suspend #待機
systemctl hibernate #休眠
systemctl rescue #進入單使用者模式
1.3.3.2. 分析系統狀態
主要是檢視系統中納入systemd管理的服務的狀態
systemctl status #系統狀態
systemctl list-units #所有啟用單元列表
systemctl --failed #執行失敗單元列表
# 列出所有配置檔案
$ systemctl list-unit-files
# 列出指定型別的配置檔案
$ systemctl list-unit-files --type=service
1.3.3.3. 單元的管理
使用systemd操作單元的啟用與關閉
systemctl start <unit> #立即啟用單元
systemctl stop <unit> #立即關閉單元
sudo systemctl kill <unit> #前面的stop不好使了,就強行殺死這個單元
systemctl restart <unit> #重啟單元
systemctl status <unit> #單元狀態,這是和好用的指令,能夠看到服務單元的幾乎所有資訊
systemctl is-enabled <unit> #單元是否配置自動啟動
systemctl enable <unit># 配置自動啟動單元
systemctl disable <unit>#關閉單元自動啟動
systemctl help <unit>#單元幫助手冊,一般是服務單元
systemctl daemon-reload <unit>#掃描單元配置檔案變動,重新載入
systemctl mask <unit> #禁用單元
systemctl unmask <unit>#取消禁用
下面是我本人計算機上mysql的狀態資訊:
systemctl status mysql.service
● mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running)
Process: 2666 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS)
Process: 2602 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
Main PID: 2668 (mysqld)
Tasks: 27 (limit: 4915)
Memory: 218.1M
CGroup: /system.slice/mysql.service
└─2668 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
- loaded,單元配置檔案地址
- active:啟用狀態
- process:開啟服務時執行的指令
- main Pid:主程式ID
- memory:佔用記憶體
- CGroup:systemd通過CGroup控制程式,這裡展示該服務的所有子程式
1.3.3.4. 單元的依賴列表
systemctl list-depandencies <xxx.service> #列出xxx.service的依賴單元
在systemd中的單元的依賴關係
1.3.3.5. 其他
一些雜七雜八的指令
systemd-analyze #系統啟動時間統計
systemd-analyze blame #檢視所有服務啟動時間列表,blame就能看出,這是要等一個背鍋位
localectl #本地化資訊
timedatectl #時區資訊
loginctl list-user #列出當前登入使用者
systemd的指令非常豐富,可以通過查詢文件獲取全部指令
1.4. systemd配置
systemd的配置檔案主要分佈在兩個地方:
系統單元目錄(全域性配置,我的是/lib/systemd/system
)和系統配置目錄(區域性配置,我的是/etc/systemd/system
)
你可以通過下面的指令查詢配置目錄:
pkg-config systemd --variable=systemdsystemunitdir #單元目錄
pkg-config systemd --variable=systemdsystemconfdir #配置目錄
其實配置目錄的很多檔案都是指向單元目錄的軟連結。
單元配置檔案就像一個藍圖,定義了一個單元的依賴關係、啟動順序、開啟關閉指令或者掛載點等,
systemd就是讀取這些資訊來管理單元的。
1.4.1. Service檔案
在systemd中一個.service
就是一個服務型別的配置單元,同時也代表了一個服務功能。
我們使用sysctemctl cat ssh.service
來檢視ssh.service檔案內容,該檔案就在/lib/systemd/system
下.
注:這個Service只有在你安裝openssh-server之後才會有.
[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Alias=sshd.service
可以看到service檔案分為Unit
/Service
/Install
三個區塊,我們分開解釋
1.4.1.1. [Unit]
主要描述啟動順序與依賴關係
[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
Description,一段描述Service的資訊
After,表示ssh.service
在network.target auditd.service
單元之後啟動。另外還有一個屬性Before
,
表示當前單元在列出的單元之前啟動。比如Before=bar.service
,說明當前單元在bar.service
之前啟動。
After
、Before
定義了單元之間啟動的順序
ConditionPathExists,表示在後面的路徑存在時返回true,這裡使用了!
非運算子,應該是取反的意思。
同樣還有其他幾個路徑判斷條件——ConditionPathIsDirectory
、ConditionFileNotEmpty
,顧名思義,他們的
意義應該不難猜吧。這些條件必須返回為true
,否則該單元不會執行
1.4.1.2. [Service]
這個區塊定義如何啟動當前服務
[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
EnvironmentFile,指定當前服務環境引數檔案,內部使用鍵值對定義,可以使用$key
讀取值,比如後面的$SSHD_OPTS
ExecStartPre,定義啟動服務前執行的指令
ExecStart,定義啟動程式執行的指令
ExecReload,表示重啟服務時執行的命令。其他的諸如ExecStop等等,望文生義即可
KillMode,定義 Systemd 如何停止 sshd 服務,process表示當kill sshd服務的時候,僅殺死主程式,子程式還是留著的。
其他的kill模式還有:
- control-group(預設值):當前控制組裡面的所有子程式,都會被殺掉
- mixed:主程式將收到 SIGTERM 訊號,子程式收到 SIGKILL 訊號
- none:沒有程式會被殺掉,只是執行服務的 stop 命令
Restart欄位,定義了 sshd 退出後,Systemd 的重啟方式。on-failure,表示任何意外的失敗,就將重啟sshd。
另外還有其他重啟模式定義:
- no(預設值):退出後不會重啟
- on-success:只有正常退出時(退出狀態碼為0),才會重啟
- on-abnormal:只有被訊號終止和超時,才會重啟
- on-abort:只有在收到沒有捕捉到的訊號終止時,才會重啟
- on-watchdog:超時退出,才會重啟
- always:不管是什麼退出原因,總是重啟
最後一個比較重要的是Type欄位,定義啟動型別。notify,表示啟動結束後會發出通知訊號,然後 Systemd 再啟動其他服務。
其他的型別如下:
- simple(預設值):ExecStart欄位啟動的程式為主程式
- forking:ExecStart欄位將以fork()方式啟動,此時父程式將會退出,子程式將成為主程式
- oneshot:類似於simple,但只執行一次,Systemd 會等它執行完,才啟動其他服務
- dbus:類似於simple,但會等待 D-Bus 訊號後啟動
1.4.1.3. [Install]
定義如何安裝這個配置檔案,即怎樣做到開機啟動
WantedBy欄位:表示該服務所在的Target。
Target的含義是服務組,表示一組服務。WantedBy=multi-user.target指的是,sshd 所在的 Target 是multi-user.target。
systemctl enable sshd.service
其實就是將sshd服務的連結放在multi-user.target.wants
目錄下。
同時multi-user.target
是系統的預設target,在啟動該target的時候,他下面的服務都會開機啟動。
這也就是隻要掛上multi-user.target
就能開機啟動的原因
1.4.2. target檔案
執行systemctl cat multi-user.target
,可得:
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes
target檔案只是組織一批服務,因此他沒有[service]、[mount]等定義啟動或者掛載的區塊
Requires,表示強依賴關係,即必須要求basic.target
啟動,否則multi-user啟動失敗。
其他的依賴關係如下:
- Wants,只用於啟用依賴,沒有強依賴關係,該服務沒起來也不影響當前服務
- Conflicts,衝突關係,有我沒他,否則不能執行
- Requisite,前置依賴,當前單元啟用前,必須啟用它,否則失敗,屬於強依賴
Wants是比較重要的依賴關係,他不會將啟動錯誤擴散給其他單元。systemd文件鼓勵我們多用Wants關係
AllowIsolate,表示允許使用systemctl isolate命令切換到multi-user.target
1.5. systemd日誌服務
systemd 自帶日誌服務 journald,該日誌服務的設計初衷是克服現有的 syslog 服務的缺點。
- syslog 不安全,訊息的內容無法驗證
- 資料沒有嚴格的格式,非常隨意
Systemd Journal 用二進位制格式儲存所有日誌資訊,使用者使用 journalctl 命令來檢視日誌資訊。無需自己編寫複雜脆弱的字串分析處理程式。
常見的指令如下:
# 檢視所有日誌(預設情況下 ,只儲存本次啟動的日誌)
$ sudo journalctl
# 檢視核心日誌(不顯示應用日誌)
$ sudo journalctl -k
# 檢視系統本次啟動的日誌
$ sudo journalctl -b
$ sudo journalctl -b -0
# 檢視上一次啟動的日誌(需更改設定)
$ sudo journalctl -b -1
# 檢視指定時間的日誌
$ sudo journalctl --since="2012-10-30 18:17:16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"
# 顯示尾部的最新10行日誌
$ sudo journalctl -n
# 顯示尾部指定行數的日誌
$ sudo journalctl -n 20
# 實時滾動顯示最新日誌
$ sudo journalctl -f
# 檢視指定服務的日誌
$ sudo journalctl /usr/lib/systemd/systemd
# 檢視指定程式的日誌
$ sudo journalctl _PID=1
# 檢視某個路徑的指令碼的日誌
$ sudo journalctl /usr/bin/bash
# 檢視指定使用者的日誌
$ sudo journalctl _UID=33 --since today
# 檢視某個 Unit 的日誌
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today
# 實時滾動顯示某個 Unit 的最新日誌
$ sudo journalctl -u nginx.service -f
# 合併顯示多個 Unit 的日誌
$ journalctl -u nginx.service -u php-fpm.service --since today
# 檢視指定優先順序(及其以上級別)的日誌,共有8級
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b
# 日誌預設分頁輸出,--no-pager 改為正常的標準輸出
$ sudo journalctl --no-pager
# 以 JSON 格式(單行)輸出
$ sudo journalctl -b -u nginx.service -o json
# 以 JSON 格式(多行)輸出,可讀性更好
$ sudo journalctl -b -u nginx.serviceqq
-o json-pretty
# 顯示日誌佔據的硬碟空間
$ sudo journalctl --disk-usage
# 指定日誌檔案佔據的最大空間
$ sudo journalctl --vacuum-size=1G
# 指定日誌檔案儲存多久
$ sudo journalctl --vacuum-time=1years
1.6. 在systemd中新增單元
關於自定義單元的首要一點建議:不要更改/lib/systemd/system
(系統單元目錄),他由系統維護。
我們一般在/etc/systemd/system
下自定義啟動單元。
1.6.1. 寫一個小栗子
- 建立一個名為
test1.target
的單元
[Unit]
Description=test 1
- 建立
test2.target
,依賴與test1
[Unit]
Description=test 2
Wants=test1.target
- 啟用test2.target,test1作為依賴也會被啟用
systemctl start test2.target
- 驗證兩個是否都被啟用
systemctl status test1.target test2.target
注:如果單元內包含[Install]模組,需要在start前enable他.
systemctl enable <unit>
- 刪除單元
systemctl stop <unit> #首先停止單元
systemctl disable <unit> #如果有[Install]模組,則刪除連線符號
#最後刪除單元檔案即可
1.7. systemd 的按需和資源並行啟動
這是一個很複雜的概念,最好單獨討論