init
- sysvinit -> upstart -> systemd
在 Debain 8.0 Jessie 將傳統的 sysvinit 改為 systemd 之後,Ubuntu 在未來的新版本也將以 systemd 取代目前的 upstart 作為 init 行程。
init程式
Linux 操作系統的啟動首先從 BIOS 開始,接下來進入 boot loader,由 bootloader 載入內核,進行內核初始化。內核初始化的最後一步就是啟動 pid 為 1 的 init 進程。這個進程是系統的第一個進程。它負責產生其他所有用戶進程。
init 以守護進程(daemon)方式存在,是所有其他進程的祖先。init 進程非常獨特,能夠完成其他進程無法完成的任務。init 系統能夠定義、管理和控制 init 進程的行為。它負責組織和運行許多獨立的或相關的始化工作(因此被稱為 init 系統),從而讓計算機系統進入某種用戶預訂的運行模式。
僅僅將內核運行起來是毫無實際用途的,必須由 init 系統將系統代入可操作狀態。比如啟動外殼 shell 後,便有了人機交互,這樣就可以讓計算機執行一些預訂程序完成有實際意義的任務。或者啟動 X 圖形系統以便提供更佳的人機界面,更加高效的完成任務。這裡,字符界面的 shell 或者 X 系統都是一種預設的運行模式。
大多數 Linux 發行版的 init 系統是和 System V 相兼容的,被稱為 sysvinit。這是人們最熟悉的 init 系統。一些發行版如 Slackware 採用的是 BSD 風格 Init 系統,這種風格使用較少,本文不再涉及。其他的發行版如 Gentoo 是自己定製的。Ubuntu 和 RHEL 採用 upstart 替代了傳統的 sysvinit。而 Fedora 從版本 15 開始使用了一個被稱為 systemd 的新 init 系統。
可以看到不同的發行版採用了不同的 init 實現,以下討論 Init 系統:sysvinit,UpStart 和 systemd。瞭解它們各自的設計特點,並簡要介紹它們的使用。
在 Linux 主要應用於服務器和 PC 機的時代,SysVinit 運行非常良好,概念簡單清晰。它主要依賴於 Shell 腳本,這就決定了它的最大弱點:啟動太慢。在很少重新啟動的 Server 上,這個缺點並不重要。而當 Linux 被應用到移動終端設備的時候,啟動慢就成了一個大問題。為了更快地啟動,人們開始改進 sysvinit,先後出現了 upstart 和 systemd 這兩個主要的新一代 init 系統。Upstart 已經開發了 8 年多,在不少系統中已經替換 sysvinit。Systemd 出現較晚,但發展更快,大有取代 upstart 的趨勢。
sysvinit
sysvinit 就是 system V 風格的 init 系統,顧名思義,它源於 System V 系列 UNIX。它提供了比 BSD 風格 init 系統更高的靈活性。是已經風行了幾十年的 UNIX init 系統,一直被各類 Linux 發行版改採用。
Sysvinit 用術語 runlevel 來定義"預訂的運行模式"。Sysvinit 檢查 ‘/etc/inittab’ 文件中是否含有 ‘initdefault’ 項。 這告訴 init 系統是否有一個默認運行模式。如果沒有默認的運行模式,那麼用戶將進入系統控制台,手動決定進入何種運行模式。
sysvinit 中運行模式描述了系統各種預訂的運行模式。通常會有 8 種運行模式,即運行模式 0 到 6 和 S 或者 s。
- 每種 Linux 發行版對運行模式的定義都不太一樣。但 0,1,6 卻得到了大家的一致贊同:
- 0 關機
- 1 單用戶模式
- 6 重啟
通常在 /etc/inittab 文件中定義了各種運行模式的工作範圍。比如 RedHat 定義了 runlevel 3 和 5。運行模式 3 將系統初始化為字符界面的 shell 模式;運行模式 5 將系統初始化為 GUI 模式。無論是命令行界面還是 GUI,運行模式 3 和 5 相對於其他運行模式而言都是完整的正式的運行狀態,計算機可以完成用戶需要的任務。而模式 1,S 等往往用於系統故障之後的排錯和恢復。
很顯然,這些不同的運行模式下系統需要初始化運行的進程和需要進行的初始化準備都是不同的。比如運行模式 3 不需要啟動 X 系統。用戶只需要指定需要進入哪種模式,sysvinit 將負責執行所有該模式所必須的初始化工作。
sysvinit 運行順序
Sysvinit 巧妙地用腳本,文件命名規則和軟鏈接來實現不同的 runlevel。首先,sysvinit 需要讀取/etc/inittab 文件。分析這個文件的內容,它獲得以下一些配置信息:
- 系統需要進入的 runlevel
- 捕獲組合鍵的定義
- 定義電源 fail/restore 腳本
- 啟動 getty 和虛擬控制台
得到配置信息後,sysvinit 順序地執行以下這些步驟,從而將系統初始化為預訂的 runlevel X。
- /etc/rc.d/rc.sysinit
- /etc/rc.d/rc 和/etc/rc.d/rcX.d/ (X 代表運行級別 0-6)
- /etc/rc.d/rc.local(通常為使用者自訂)
- X Display Manager(如果需要的話)
首先,運行 rc.sysinit 以便執行一些重要的系統初始化任務。在 RHEL5 中,rc.sysinit 主要完成以下這些工作。
- 激活 udev 和 selinux
- 設置定義在/etc/sysctl.conf 中的內核參數
- 設置系統時鐘
- 加載 keymaps
- 使能交換分區
- 設置主機名(hostname)
- 根分區檢查和 remount
- 激活 RAID 和 LVM 設備
- 開啟磁盤配額
- 檢查並掛載所有文件系統
- 清除過期的 locks 和 PID 文件
完成了以上這些工作之後,sysvinit 開始運行/etc/rc.d/rc 腳本。根據不同的 runlevel,rc 腳本將打開對應該 runlevel 的 rcX.d 目錄(X 就是 runlevel),找到並運行存放在該目錄下的所有啟動腳本。每個 runlevel X 都有一個這樣的目錄,目錄名為/etc/rc.d/rcX.d。
在這些目錄下存放著很多不同的腳本。文件名以 S 開頭的腳本就是啟動時應該運行的腳本,S 後面跟的數字定義了這些腳本的執行順序。在/etc/rc.d/rcX.d 目錄下的腳本其實都是一些軟鏈接文件,真實的腳本文件存放在/etc/init.d 目錄下。如下所示:
//etc/rc5.d
[root@www ~]# ll /etc/rc5.d/
lrwxrwxrwx 1 root root 16 Sep 4 2008 K02dhcdbd -> ../init.d/dhcdbd
....(中間省略)....
lrwxrwxrwx 1 root root 14 Sep 4 2008 K91capi -> ../init.d/capi
lrwxrwxrwx 1 root root 23 Sep 4 2008 S00microcode_ctl -> ../init.d/microcode_ctl
lrwxrwxrwx 1 root root 22 Sep 4 2008 S02lvm2-monitor -> ../init.d/lvm2-monitor
....(中間省略)....
lrwxrwxrwx 1 root root 17 Sep 4 2008 S10network -> ../init.d/network
....(中間省略)....
lrwxrwxrwx 1 root root 11 Sep 4 2008 S99local -> ../rc.local
lrwxrwxrwx 1 root root 16 Sep 4 2008 S99smartd -> ../init.d/smartd
....(底下省略)....
- 當所有的初始化腳本執行完畢。Sysvinit 運行/etc/rc.d/rc.local 腳本。
- rc.local 是 Linux 留給用戶進行個性化設置的地方。您可以把自己私人想設置和啟動的東西放到這裡,一台 Linux Server 的用戶一般不止一個,所以才有這樣的考慮。
Sysvinit 和系統關閉
Sysvinit 不僅需要負責初始化系統,還需要負責關閉系統。在系統關閉時,為了保證數據的一致性,需要小心地按順序進行結束和清理工作。
比如應該先停止對文件系統有讀寫操作的服務,然後再 umount 文件系統。否則數據就會丟失。
這種順序的控制這也是依靠/etc/rc.d/rcX.d/目錄下所有腳本的命名規則來控制的,在該目錄下所有以 K 開頭的腳本都將在關閉系統時調用,字母 K 之後的數字定義了它們的執行順序。
這些腳本負責安全地停止服務或者其他的關閉工作。
Sysvinit 的管理和控制功能
此外,在系統啟動之後,管理員還需要對已經啟動的進程進行管理和控制。原始的 sysvinit 軟件包包含了一系列的控制啟動,運行和關閉所有其他程序的工具。
halt
- 停止系統。
init
- 這個就是 sysvinit 本身的 init 進程實體,以 pid1 身份運行,是所有用戶進程的父進程。最主要的作用是在啟動過程中使用/etc/inittab 文件創建進程。
killall5
- 就是 SystemV 的 killall 命令。向除自己的會話(session)進程之外的其它進程發出信號,所以不能殺死當前使用的 shell。
last
- 回溯/var/log/wtmp 文件(或者-f 選項指定的文件),顯示自從這個文件建立以來,所有用戶的登錄情況。
lastb
- 作用和 last 差不多,默認情況下使用/var/log/btmp 文件,顯示所有失敗登錄企圖。
mesg
- 控制其它用戶對用戶終端的訪問。
pidof
- 找出程序的進程識別號(pid),輸出到標準輸出設備。
poweroff
- 等於 shutdown -h –p,或者 telinit 0。關閉系統並切斷電源。
reboot
- 等於 shutdown –r 或者 telinit 6。重啟系統。
runlevel
- 讀取系統的登錄記錄文件(一般是/var/run/utmp)把以前和當前的系統運行級輸出到標準輸出設備。
shutdown
- 以一種安全的方式終止系統,所有正在登錄的用戶都會收到系統將要終止通知,並且不准新的登錄。
sulogin
- 當系統進入單用戶模式時,被 init 調用。當接收到啟動加載程序傳遞的-b 選項時,init 也會調用 sulogin。
telinit
- 實際是 init 的一個連接,用來向 init 傳送單字符參數和信號。
utmpdump
- 以一種用戶友好的格式向標準輸出設備顯示/var/run/utmp 文件的內容。
wall
- 向所有有信息權限的登錄用戶發送消息。
不同的 Linux 發行版在這些 sysvinit 的基本工具基礎上又開發了一些輔助工具用來簡化 init 系統的管理工作。比如 RedHat 的 RHEL 在 sysvinit 的基礎上開發了 initscripts 軟件包,包含了大量的啟動腳本 (如 rc.sysinit) ,還提供了 service,chkconfig 等命令行工具,甚至一套圖形化界面來管理 init 系統。其他的 Linux 發行版也有各自的 initscript 或其他名字的 init 軟件包來簡化 sysvinit 的管理。
Sysvinit 的小結
Sysvinit 的優點是概念簡單。Service 開發人員只需要編寫啟動和停止腳本,概念非常清楚;將 service 添加/刪除到某個 runlevel 時,只需要執行一些創建/刪除軟連接文件的基本操作;這些都不需要學習額外的知識或特殊的定義語法(UpStart 和 Systemd 都需要用戶學習新的定義系統初始化行為的語言)。
其次,sysvinit 的另一個重要優點是確定的執行順序:腳本嚴格按照啟動數字的大小順序執行,一個執行完畢再執行下一個,這非常有益於錯誤排查。UpStart 和 systemd 支持並發啟動,導致沒有人可以確定地瞭解具體的啟動順序,排錯不易。
但是串行地執行腳本導致 sysvinit 運行效率較慢,在新的 IT 環境下,啟動快慢成為一個重要問題。此外動態設備加載等 Linux 新特性也暴露出 sysvinit 設計的一些問題。針對這些問題,人們開始想辦法改進 sysvinit,以便加快啟動時間,並解決 sysvinit 自身的設計問題。
Upstart
Upstart是一個基於事件的初始化常駐程式 (更精確的來說,是指用來將系統初始化的程式),用於替代傳統的init(多種類Unix電腦作業系統啟動時用於執行任務的程式)。假如您使用的 Linux 發行版是 Ubuntu,很可能會發現在您的計算機上找不到/etc/inittab 文件了,這是因為 Ubuntu 使用了一種被稱為 upstart 的新型 init 系統。
大約在 2006 年或者更早的時候, Ubuntu 開發人員試圖將 Linux 安裝在筆記本電腦上。在這期間技術人員發現經典的 sysvinit 存在一些問題:它不適合筆記本環境。這促使程序員 Scott James Remnant 著手開發 upstart。
傳統的init行程原本只負責在開機後將電腦帶入正常執行狀態,並且在正常關機前關閉服務。因此,它的設計是嚴格同步的,且會阻塞未來的任務,直到完成當前任務。因為受限於準備或清除函式,它的任務也是事先定義的。這使得它無法簡潔地處理現代桌面電腦上的各種非啟動任務,其中包括:
+ 機器執行時添加或刪除USB隨身碟和其他可攜式儲存/網路裝置
+ 發現並掃描新儲存裝置而不鎖定系統,尤其是當磁碟沒被掃描就不執行時
+ 韌體需要在檢測裝置之後、在它可用之前載入
當 Linux 內核進入 2.6 時代時,內核功能有了很多新的更新。新特性使得 Linux 不僅是一款優秀的服務器操作系統,也可以被用於桌面系統,甚至嵌入式設備。桌面系統或便攜式設備的一個特點是經常重啟,而且要頻繁地使用硬件熱插拔技術。 在現代計算機系統中,硬件繁多、接口有限,人們並非將所有設備都始終連接在計算機上,比如 U 盤平時並不連接電腦,使用時才插入 USB 插口。因此,當系統上電啟動時,一些外設可能並沒有連接。而是在啟動後當需要的時候才連接這些設備。在 2.6 內核支持下,一旦新外設連接到系統,內核便可以自動實時地發現它們,並初始化這些設備,進而使用它們。這為便攜式設備用戶提供了很大的靈活性。
可是這些特性為 sysvinit 帶來了一些挑戰。當系統初始化時,需要被初始化的設備並沒有連接到系統上;比如打印機。為了管理打印任務,系統需要啟動 CUPS 等服務,而如果打印機沒有接入系統的情況下,啟動這些服務就是一種浪費。Sysvinit 沒有辦法處理這類需求,它必須一次性把所有可能用到的服務都啟動起來,即使打印機並沒有連接到系統,CUPS 服務也必須啟動。
還有網絡共享盤的掛載問題。在/etc/fstab 中,可以指定系統自動掛載一個網絡盤,比如 NFS,或者 iSCSI 設備。在本文的第一部分 sysvinit 的簡介中可以看到,sysvinit 分析/etc/fstab 掛載文件系統這個步驟是在網絡啟動之前。可是如果網絡沒有啟動,NFS 或者 iSCSI 都不可訪問,當然也無法進行掛載操作。Sysvinit 採用 netdev 的方式來解決這個問題,即/etc/fstab 發現 netdev 屬性掛載點的時候,不嘗試掛載它,在網絡初始化並使能之後,還有一個專門的 netfs 服務來掛載所有這些網絡盤。這是一個不得已的補救方法,給管理員帶來不便。部分新手管理員甚至從來也沒有聽說過 netdev 選項,因此經常成為系統管理的一個陷阱。
針對以上種種情況,Ubuntu 開發人員在評估了當時的幾個可選 init 系統之後,決定重新設計和開發一個全新的 init 系統,即 UpStart。UpStart 基於事件機制,比如 U 盤插入 USB 接口後,udev 得到內核通知,發現該設備,這就是一個新的事件。UpStart 在感知到該事件之後觸發相應的等待任務,比如處理/etc/fstab 中存在的掛載點。採用這種事件驅動的模式,upstart 完美地解決了即插即用設備帶來的新問題。
此外,採用事件驅動機制也帶來了一些其它有益的變化,比如加快了系統啟動時間。sysvinit 運行時是同步阻塞的。一個腳本運行的時候,後續腳本必須等待。這意味著所有的初始化步驟都是串行執行的,而實際上很多服務彼此並不相關,完全可以並行啟動,從而減小系統的啟動時間。在 Linux 大量應用於服務器的時代,系統啟動時間也許還不那麼重要;然而對於桌面系統和便攜式設備,啟動時間的長短對用戶體驗影響很大。此外云計算等新的 Server 端技術也往往需要單個設備可以更加快速地啟動。UpStart 滿足了這些需求,目前不僅桌面系統 Ubuntu 採用了 UpStart,甚至企業級服務器級的 RHEL 也默認採用 UpStart 來替換 sysvinit 作為 init 系統。
Upstart 的特點
UpStart 解決了之前提到的 sysvinit 的缺點。採用事件驅動模型,UpStart 可以:
- 更快地啟動系統
- 當新硬件被發現時動態啟動服務
- 硬件被拔除時動態停止服務
這些特點使得 UpStart 可以很好地應用在桌面或者便攜式系統中,處理這些系統中的動態硬件插拔特性。
Upstart 概念和術語
Upstart 的基本概念和設計清晰明確。UpStart 主要的概念是 job 和 event。Job 就是一個工作單元,用來完成一件工作,比如啟動一個後台服務,或者運行一個配置命令。每個 Job 都等待一個或多個事件,一旦事件發生,upstart 就觸發該 job 完成相應的工作。
Job: 就是一個工作的單元,一個任務或者一個服務。可以理解為 sysvinit 中的一個服務腳本。有三種類型的工作:
- task job;
- service job;
- abstract job;
task job 代表在一定時間內會執行完畢的任務,比如刪除一個文件;
service job 代表後台服務進程,比如 apache httpd。這裡進程一般不會退出,一旦開始運行就成為一個後台精靈進程,由 init 進程管理,如果這類進程退出,由 init 進程重新啟動,它們只能由 init 進程發送信號停止。它們的停止一般也是由於所依賴的停止事件而觸發的,不過 upstart 也提供命令行工具,讓管理人員手動停止某個服務;
Abstract job 僅由 upstart 內部使用,僅對理解 upstart 內部機理有所幫助。我們不用關心它。
除了以上的分類之外,還有另一種工作(Job)分類方法。Upstart 不僅可以用來為整個系統的初始化服務,也可以為每個用戶會話(session)的初始化服務。系統的初始化任務就叫做 system job,比如掛載文件系統的任務就是一個 system job;用戶會話的初始化服務就叫做 session job。
Job 生命週期
Upstart 為每個工作都維護一個生命週期。一般來說,工作有開始,運行和結束這幾種狀態。為了更精細地描述工作的變化,Upstart 還引入了一些其它的狀態。比如開始就有開始之前(pre-start),即將開始(starting)和已經開始了(started)幾種不同的狀態,這 樣可以更加精確地描述工作的當前狀態。
工作從某種初始狀態開始,逐漸變化,或許要經歷其它幾種不同的狀態,最終進入另外一種狀態,形成一個狀態機。在這個過程中,當工作的狀態即將發生變化的時候,init 進程會發出相應的事件(event)。
表 1.Upstart 中 Job 的可能狀態 |狀態名| 含義| |---| ---| |Waiting| 初始狀態| |Starting| Job 即將開始| |pre-start| 執行 pre-start 段,即任務開始前應該完成的工作| |Spawned| 準備執行 script 或者 exec 段| |post-start| 執行 post-start 動作| |Running| interim state set after post-start section processed denoting job is running (But it may have no associated PID!)| |pre-stop| 執行 pre-stop 段| |Stopping| interim state set after pre-stop section processed| |Killed| 任務即將被停止| |post-stop| 執行 post-stop 段|
- 其中有四個狀態會引起 init 進程發送相應的事件,表明該工作的相應變化,而其它的狀態變化不會發出事件。
- Starting
- Started
- Stopping
- Stopped
事件 Event
顧名思義,Event 就是一個事件。事件在 upstart 中以通知消息的形式具體存在。一旦某個事件發生了,Upstart 就向整個系統發送一個消息。沒有任何手段阻止事件消息被 upstart 的其它部分知曉,也就是說,事件一旦發生,整個 upstart 系統中所有工作和其它的事件都會得到通知。
Event 可以分為三類: signal,methods 或者 hooks。
- Signal: 事件是非阻塞的,異步的。發送一個信號之後控制權立即返回。
- Methods: 事件是阻塞的,同步的。
- Hooks: 事件是阻塞的,同步的。它介於 Signals 和 Methods 之間,調用發出 Hooks 事件的進程必須等待事件完成才可以得到控制權,但不檢查事件是否成功。
事件是個非常抽象的概念,下面我羅列出一些常見的事件,希望可以幫助您進一步瞭解事件的含義:
- 系統上電啟動,init 進程會發送"start"事件
- 根文件系統可寫時,相應 job 會發送文件系統就緒的事件
- 一個塊設備被發現並初始化完成,發送相應的事件
- 某個文件系統被掛載,發送相應的事件
- 類似 atd 和 cron,可以在某個時間點,或者週期的時間點發送事件
- 另外一個 job 開始或結束時,發送相應的事件
- 一個磁盤文件被修改時,可以發出相應的事件
- 一個網絡設備被發現時,可以發出相應的事件
- 缺省路由被添加或刪除時,可以發出相應的事件
不同的 Linux 發行版對 upstart 有不同的定製和實現,實現和支持的事件也有所不同,可以用man 7 upstart-events來查看事件列表。
Job 和 Event 的相互協作
Upstart 就是由事件觸發工作運行的一個系統,每一個程序的運行都由其依賴的事件發生而觸發的。
系統初始化的過程是在工作和事件的相互協作下完成的,可以大致描述如下:系統初始化時,init 進程開始運行,init 進程自身會發出不同的事件,這些最初的事件會觸發一些工作運行。每個工作運行過程中會釋放不同的事件,這些事件又將觸發新的工作運行。如此反覆,直到整個系統正常運行起來。
究竟哪些事件會觸發某個工作的運行?這是由工作配置文件定義的。