近年來,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 的可能狀态

圖 1. Job’s life cycle
其中有四個狀态會引起 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 啟動過程
系統上電後運作 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 指令對照表
很多情況下管理者并不喜歡子指令風格,因為需要手動鍵入的字元太多。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。
轉載自劉明先生的系列文章:http://www.ibm.com/developerworks/cn/linux/1407_liuming_init2/index.html