淺析 Linux 初始化 init 系統(2): UpStart

發表於2015-03-16

近年來,Linux 系統的 init 程式經歷了兩次重大的演進,傳統的 sysvinit 已經淡出歷史舞臺,新系統 UpStart 和 systemd 各有特點,而越來越多的 Linux 發行版採納了 systemd。本文簡要介紹了這三種 init 系統的使用和原理,每個 Linux 系統管理員和系統軟體開發者都應該瞭解它們,以便更好地管理系統和開發應用。本文是系列的第二部分,主要講述 UpStart 的特點和使用。

Upstart 簡介

假如您使用的 Linux 發行版是 Ubuntu,很可能會發現在您的計算機上找不到/etc/inittab 檔案了,這是因為 Ubuntu 使用了一種被稱為 upstart 的新型 init系統。

開發 Upstart 的緣由

大約在 2006 年或者更早的時候, Ubuntu 開發人員試圖將 Linux 安裝在膝上型電腦上。在這期間技術人員發現經典的 sysvinit 存在一些問題:它不適合筆記本環境。這促使程式設計師 Scott James Remnant 著手開發 upstart。 當 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

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 段

圖 1 展示了 Job 的狀態機。

圖 1. Job’s life cycle

image003 其中有四個狀態會引起 init 程式傳送相應的事件,表明該工作的相應變化:

  • Starting
  • Started
  • Stopping
  • Stopped

而其它的狀態變化不會發出事件。那麼我們接下來就來看看事件的詳細含義吧。

事件 Event

顧名思義,Event 就是一個事件。事件在 upstart 中以通知訊息的形式具體存在。一旦某個事件發生了,Upstart 就向整個系統傳送一個訊息。沒有任何手段阻止事件訊息被 upstart 的其它部分知曉,也就是說,事件一旦發生,整個 upstart 系統中所有工作和其它的事件都會得到通知。 Event 可以分為三類: signal,methods 或者 hooks。 Signals Signal 事件是非阻塞的,非同步的。傳送一個訊號之後控制權立即返回。 Methods Methods 事件是阻塞的,同步的。 Hooks Hooks 事件是阻塞的,同步的。它介於 Signals 和 Methods 之間,呼叫發出 Hooks 事件的程式必須等待事件完成才可以得到控制權,但不檢查事件是否成功。 事件是個非常抽象的概念,下面我羅列出一些常見的事件,希望可以幫助您進一步瞭解事件的含義:

  • 系統上電啟動,init 程式會傳送”start”事件
  • 根檔案系統可寫時,相應 job 會傳送檔案系統就緒的事件
  • 一個塊裝置被發現並初始化完成,傳送相應的事件
  • 某個檔案系統被掛載,傳送相應的事件
  • 類似 atd 和 cron,可以在某個時間點,或者週期的時間點傳送事件
  • 另外一個 job 開始或結束時,傳送相應的事件
  • 一個磁碟檔案被修改時,可以發出相應的事件
  • 一個網路裝置被發現時,可以發出相應的事件
  • 預設路由被新增或刪除時,可以發出相應的事件

不同的 Linux 發行版對 upstart 有不同的定製和實現,實現和支援的事件也有所不同,可以用man 7 upstart-events來檢視事件列表。

Job 和 Event 的相互協作

Upstart 就是由事件觸發工作執行的一個系統,每一個程式的執行都由其依賴的事件發生而觸發的。 系統初始化的過程是在工作和事件的相互協作下完成的,可以大致描述如下:系統初始化時,init 程式開始執行,init 程式自身會發出不同的事件,這些最初的事件會觸發一些工作執行。每個工作執行過程中會釋放不同的事件,這些事件又將觸發新的工作執行。如此反覆,直到整個系統正常執行起來。 究竟哪些事件會觸發某個工作的執行?這是由工作配置檔案定義的。

工作配置檔案

任何一個工作都是由一個工作配置檔案(Job Configuration File)定義的。這個檔案是一個文字檔案,包含一個或者多個小節(stanza)。每個小節是一個完整的定義模組,定義了工作的一個方面,比如 author 小節定義了工作的作者。工作配置檔案存放在/etc/init 下面,是以.conf 作為檔案字尾的檔案。

清單 1. 一個最簡單的工作配置檔案

上面的例子不會產生任何作用,一個真正的工作配置檔案會包含很多小節,其中比較重要的小節有以下幾個: “expect” Stanza Upstart 除了負責系統的啟動過程之外,和 SysVinit 一樣,Upstart 還提供一系列的管理工具。當系統啟動之後,管理員可能還需要進行維護和調整,比如啟動或者停止某項系統服務。或者將系統切換到其它的工作狀態,比如改變執行級別。本文後續將詳細介紹 Upstart 的管理工具的使用。 為了啟動,停止,重啟和查詢某個系統服務。Upstart 需要跟蹤該服務所對應的程式。比如 httpd 服務的程式 PID 為 1000。當使用者需要查詢 httpd 服務是否正常執行時,Upstart 就可以利用 ps 命令查詢程式 1000,假如它還在正常執行,則表明服務正常。當使用者需要停止 httpd 服務時,Upstart 就使用 kill 命令終止該程式。為此,Upstart 必須跟蹤服務程式的程式號。 部分服務程式為了將自己變成後臺精靈程式(daemon),會採用兩次派生(fork)的技術,另外一些服務則不會這樣做。假如一個服務派生了兩次,那麼 UpStart 必須採用第二個派生出來的程式號作為服務的 PID。但是,UpStart 本身無法判斷服務程式是否會派生兩次,為此在定義該服務的工作配置檔案中必須寫明 expect 小節,告訴 UpStart 程式是否會派生兩次。 Expect 有兩種,”expect fork”表示程式只會 fork 一次;”expect daemonize”表示程式會 fork 兩次。 “exec” Stanza 和”script” Stanza 一個 UpStart 工作一定需要做些什麼,可能是執行一條 shell 命令,或者執行一段指令碼。用”exec”關鍵字配置工作需要執行的命令;用”script”關鍵字定義需要執行的指令碼。 清單 2 顯示了 exec 和 script 的用法:

清單 2.script 例子

這是 mountall 的例子,該工作在系統啟動時執行,負責掛載所有的檔案系統。該工作需要執行復雜的指令碼,由”script”關鍵字定義;在指令碼中,使用了 exec 來執行 mountall 命令。 “start on” Stanza 和”stop on” Stanza “start on”定義了觸發工作的所有事件。”start on”的語法很簡單,如下所示: start on EVENT [[KEY=]VALUE]… [and|or…] EVENT 表示事件的名字,可以在 start on 中指定多個事件,表示該工作的開始需要依賴多個事件發生。多個事件之間可以用 and 或者 or 組合,”表示全部都必須發生”或者”其中之一發生即可”等不同的依賴條件。除了事件發生之外,工作的啟動還可以依賴特定的條件,因此在 start on 的 EVENT 之後,可以用 KEY=VALUE 來表示額外的條件,一般是某個環境變數(KEY)和特定值(VALUE)進行比較。如果只有一個變數,或者變數的順序已知,則 KEY 可以省略。 “stop on”和”start on”非常類似,只不過是定義工作在什麼情況下需要停止。 程式碼清單 3 是”start on”和”stop on”的一個例子。

清單 3. start on/ stop on 例子

D-Bus 是一個系統訊息服務,上面的配置檔案表明當系統發出 local-filesystems 事件時啟動 D-Bus;當系統發出 deconfiguring-networking 事件時,停止 D-Bus 服務。

Session Init

UpStart 還可以用於管理使用者會話的初始化。在我寫這篇文章的今天,多數 Linux 發行版還沒有使用 UpStart 管理會話。只有在 Ubuntu Raring 版本中,使用 UpStart 管理使用者會話的初始化過程。 首先讓我們瞭解一下 Session 的概念。Session 就是一個使用者會話,即使用者從遠端或者本地登入系統開始工作,直到使用者退出。這整個過程就構成一個會話。 每個使用者的使用習慣和使用方法都不相同,因此使用者往往需要為自己的會話做一個定製,比如新增特定的命令別名,啟動特殊的應用程式或者服務,等等。這些工作都屬於對特定會話的初始化操作,因此可以被稱為 Session Init。 使用者使用 Linux 可以有兩種模式:字元模式和圖形介面。在字元模式下,會話初始化相對簡單。使用者登入後只能啟動一個 Shell,通過 shell 命令使用系統。各種 shell 程式都支援一個自動執行的啟動指令碼,比如~/.bashrc。使用者在這些指令碼中加入需要執行的定製化命令。字元會話需求簡單,因此這種現有的機制工作的很好。 在圖形介面下,事情就變得複雜一些。使用者登入後看到的並不是一個 shell 提示符,而是一個桌面。一個完整的桌面環境由很多元件組成。 一個桌面環境包括 window manager,panel 以及其它一些定義在/usr/share/gnome-session/sessions/下面的基本元件;此外還有一些輔助的應用程式,共同幫助構成一個完整的方便的桌面,比如 system monitors,panel applets,NetworkManager,Bluetooth,printers 等。當使用者登入之後,這些元件都需要被初始化,這個過程比字元介面要複雜的多。目前啟動各種圖形元件和應用的工作由 gnome-session 完成。過程如下: 以 Ubuntu 為例,當使用者登入 Ubuntu 圖形介面後,顯示管理器(Display Manager)lightDM 啟動 Xsession。Xsession 接著啟動 gnome-session,gnome-session 負責其它的初始化工作,然後就開始了一個 desktop session。

圖 2.傳統 desktop session 啟動過程

這個過程有一些缺點(和 sysVInit 類似)。一些應用和元件其實並不需要在會話初始化過程中啟動,更好的選擇是在需要它們的時候才啟動。比如 update-notifier 服務,該服務不停地監測幾個檔案系統路徑,一旦這些路徑上發現可以更新的軟體包,就提醒使用者。這些檔案系統路徑包括新插入的 DVD 盤等。Update-notifier 由 gnome-session 啟動並一直執行著,在多數情況下,使用者並不會插入新的 DVD,此時 update-notifier 服務一直在後臺執行並消耗系統資源。更好的模式是當使用者插入 DVD 的時候再執行 update-notifier。這樣可以加快啟動時間,減小系統執行過程中的記憶體等系統資源的開銷。對於移動,嵌入式等裝置等這還意味著省電。除了 Update-notifier 服務之外,還有其它一些類似的服務。比如 Network Manager,一天之內使用者很少切換網路裝置,所以大部分時間 Network Manager 服務僅僅是在浪費系統資源;再比如 backup manager 等其它常駐記憶體,後臺不間斷執行卻很少真正被使用的服務。 用 UpStart 的基於事件的按需啟動的模式就可以很好地解決這些問題,比如使用者插入網線的時候才啟動 Network Manager,因為使用者插入網線表明需要使用網路,這可以被稱為按需啟動。 下圖描述了採用 UpStart 之後的會話初始化過程。

圖 3.採用 Upstart 的 Desktop session init 過程

 

UpStart 使用

有兩種人員需要了解 Upstart 的使用。第一類是系統開發人員,比如 MySQL 的開發人員。它們需要了解如何編寫工作配置檔案,以便用 UpStart 來管理服務。比如啟動,停止 MySQL 服務。 另外一種情況是系統管理員,它們需要掌握 Upstart 的管理命令以便配置和管理系統的初始化,管理系統服務。

系統開發人員需要了解的 UpStart 知識

系統開發人員不僅需要掌握工作配置檔案的寫法,還需要了解一些針對服務程式程式設計上的要求。本文僅列出了少數工作配置檔案的語法。要全面掌握工作配置檔案的寫法,需要詳細閱讀 Upstart 的手冊。這裡讓我們來分析一下如何用 Upstart 來實現傳統的執行級別,進而瞭解如何靈活使用工作配置檔案。 Upstart 系統中的執行級別 Upstart 的運作完全是基於工作和事件的。工作的狀態變化和執行會引起事件,進而觸發其它工作和事件。 而傳統的 Linux 系統初始化是基於執行級別的,即 SysVInit。因為歷史的原因,Linux 上的多數軟體還是採用傳統的 SysVInit 指令碼啟動方式,並沒有為 UpStart 開發新的啟動指令碼,因此即便在 Debian 和 Ubuntu 系統上,還是必須模擬老的 SysVInit 的執行級別模式,以便和多數現有軟體相容。 雖然 Upstart 本身並沒有執行級別的概念,但完全可以用 UpStart 的工作模擬出來。讓我們完整地考察一下 UpStart 機制下的系統啟動過程。 系統啟動過程 下圖描述了 UpStart 的啟動過程。

圖 4.UpStart 啟動過程

image004 系統上電後執行 GRUB 載入核心。核心執行硬體初始化和核心自身初始化。在核心初始化的最後,核心將啟動 pid 為 1 的 init 程式,即 UpStart 程式。 Upstart 程式在執行了一些自身的初始化工作後,立即發出”startup”事件。上圖中用紅色方框加紅色箭頭表示事件,可以在左上方看到”startup”事件。 所有依賴於”startup”事件的工作被觸發,其中最重要的是 mountall。mountall 任務負責掛載系統中需要使用的檔案系統,完成相應工作後,mountall 任務會發出以下事件:local-filesystem,virtual-filesystem,all-swaps, 其中 virtual-filesystem 事件觸發 udev 任務開始工作。任務 udev 觸發 upstart-udev-bridge 的工作。Upstart-udev-bridge 會發出 net-device-up IFACE=lo 事件,表示本地迴環 IP 網路已經準備就緒。同時,任務 mountall 繼續執行,最終會發出 filesystem 事件。 此時,任務 rc-sysinit 會被觸發,因為 rc-sysinit 的 start on 條件如下:

任務 rc-sysinit 呼叫 telinit。Telinit 任務會發出 runlevel 事件,觸發執行/etc/init/rc.conf。 rc.conf 執行/etc/rc$.d/目錄下的所有指令碼,和 SysVInit 非常類似,讀者可以參考本文第一部分的描述。 程式開發時需要注意的事項 作為程式開發人員,在編寫系統服務時,需要了解 UpStart 的一些特殊要求。只有符合這些要求的軟體才可以被 UpStart 管理。 規則一,派生次數需宣告。 很多 Linux 後臺服務都通過派生兩次的技巧將自己變成後臺服務程式。如果您編寫的服務也採用了這個技術,就必須通過文件或其它的某種方式明確地讓 UpStart 的維護人員知道這一點,這將影響 UpStart 的 expect stanza,我們在前面已經詳細介紹過這個 stanza 的含義。 規則二,派生後即可用。 後臺程式在完成第二次派生的時候,必須保證服務已經可用。因為 UpStart 通過派生計數來決定服務是否處於就緒狀態。 規則三,遵守 SIGHUP 的要求。 UpStart 會給精靈程式傳送 SIGHUP 訊號,此時,UpStart 希望該精靈程式做以下這些響應工作: •完成所有必要的重新初始化工作,比如重新讀取配置檔案。這是因為 UpStart 的命令”initctl reload”被設計為可以讓服務在不重啟的情況下更新配置。 •精靈程式必須繼續使用現有的 PID,即收到 SIGHUP 時不能呼叫 fork。如果服務必須在這裡呼叫 fork,則等同於派生兩次,參考上面的規則一的處理。這個規則保證了 UpStart 可以繼續使用 PID 管理本服務。 規則四,收到 SIGTEM 即 shutdown。 •當收到 SIGTERM 訊號後,UpStart 希望精靈程式程式立即乾淨地退出,釋放所有資源。如果一個程式在收到 SIGTERM 訊號後不退出,Upstart 將對其傳送 SIGKILL 訊號。

系統管理員需要了解的 Upstart 命令

作為系統管理員,一個重要的職責就是管理系統服務。比如系統服務的監控,啟動,停止和配置。UpStart 提供了一系列的命令來完成這些工作。其中的核心是initctl,這是一個帶子命令風格的命令列工具。 比如可以用 initctl list 來檢視所有工作的概況:

這是在 Ubuntu10.10 系統上的輸出,其它的 Linux 發行版上的輸出會有所不同。第一列是工作名,比如 rsyslog。第二列是工作的目標;第三列是工作的狀態。 此外還可以用 initctl stop 停止一個正在執行的工作;用 initctl start 開始一個工作;還可以用 initctl status 來檢視一個工作的狀態;initctl restart 重啟一個工作;initctl reload 可以讓一個正在執行的服務重新載入配置檔案。這些命令和傳統的 service 命令十分相似。

表 2.service 命令和 initctl 命令對照表
Service 命令 UpStart initctl 命令
service start initctl start
service stop initctl stop
service restart initctl restart
service reload initctl reload

很多情況下管理員並不喜歡子命令風格,因為需要手動鍵入的字元太多。UpStart 還提供了一些快捷命令來簡化 initctl,實際上這些命令只是在內部呼叫相應的 initctl 命令。比如 reload,restart,start,stop 等等。啟動一個服務可以簡單地呼叫

這和執行 initctl start <job>是一樣的效果。 一些命令是為了相容其它系統(主要是 sysvinit),比如顯示 runlevel 用/sbin/runlevel 命令:

這個輸出說明當前系統的執行級別為 2。而且系統沒有之前的執行級別,也就是說在系統上電啟動進入預定執行級別之後沒有再修改過執行級別。 那麼如何修改系統上電之後的預設執行級別呢? 在 Upstart 系統中,需要修改/etc/init/rc-sysinti.conf 中的 DEFAULT_RUNLEVEL 這個引數,以便修改預設啟動執行級別。這一點和 sysvinit 的習慣有所不同,大家需要格外留意。 還有一些隨 UpStart 釋出的小工具,用來幫助開發 UpStart 或者診斷 UpStart 的問題。比如 init-checkconf 和 upstart-monitor 還可以使用 initctl 的 emit 命令從命令列傳送一個事件。

這一般是用於 UpStart 本身的排錯。

 

Upstart 小結

可以看到,UpStart 的設計比 SysVInit 更加先進。多數 Linux 發行版上已經不再使用 SysVInit,一部分發行版採用了 UpStart,比如 Ubuntu;而另外一些比如 Fedora,採用了一種被稱為 systemd 的 init 系統。Systemd 出現的比 UpStart 更晚,但發展迅速,雖然 UpStart 也還在積極開發並被越來越多地應用,但 systemd 似乎發展更快,我將在下一篇文章中再介紹 systemd。

相關文章