3、upstart介紹
upstart是一個基於事件的init的替代程式,這意味著服務的啟動和停止都基於事件的通訊。 upstart 正在由 Scott James Remnant 進行開發,早期用於Ubuntu發行版,不過它想要成為任何 Linux 發行版上 init 的通用替代程式。現在已經用在了包括Ubuntu、Fedora等主流的Linux系統中了,前面的程式碼分析就是基於upstart的。由於upstart在Ubuntu中使用最廣泛,下面的介紹以Ubuntu 10.04為原型系統。
(1)作業
Upstart init守護進程讀取/etc/init目錄下的作業設定檔,並使用inotify來監控它們的改變。設定檔名必須以.conf結尾,可以放在/etc/init/下的子目錄中。每個檔案定義一個服務或作業,其名稱按路徑名來稱呼。例如定義在/etc/init/rc-sysinit.conf中的作業就稱為rc-sysinit,而定義在/etc/init/net/apache.conf的作業稱為net/apache。這些檔案必須是純文字且不可執行檔。
作業檔案的每行由一個配置節開始,直到行尾或出現節的結束標誌。在每一個節中,一行可以用雙引號或單引號結束,也可用反斜杆繼續本行的內容。一個節和它的各個參數用空格分開。作業檔案中"#"開始的行為注釋行。常用的配置節有:
exec COMMAND [ARG]...: 定義作業要啟動並執行主進程,注意若有特殊的字元(如引號或$符)將導致整個命令被傳遞給Shell來運行。例如 exec /usr/sbin/acpid -c $EVENTSDIR -s $SOCKET。
script ... end script: 定義Shell指令碼來運行指定的主進程,該指令碼由sh來執行。Shell的-e選項總是被使用,因此任何命令失敗將導致指令碼終止。注意作業的主進程只能用exec或script節中的一種來定義,不能同時用exec和script配置節來定義。下面script配置節的一個例子:
scriptdd bs=1 if=/proc/kmsg of=$KMSGSINKexec /sbin/klogd -P $KMSGSINKend script
pre-start exec|script...: 本進程在作業的starting事件完成之後,主進程運行之前執行。通常用來準備相關環境,例如建立必要的目錄。
post-start exec|script...: 本進程在作業的started事件觸發之前,主進程產生之前執行。通常用來發送必要的命令給主進程,或者用來延遲started事件,直到主進程準備好接收用戶端的訪問。
pre-stop exec|script...: 本進程在作業被stop on節中的一個事件停止或被stop命令停止時執行。它在作業的stopping事件之前,及主進程被殺死之前執行。通常用來發送必要的shutdown命令給主進程,或調用不帶參數的start命令來取消stop。
post-stop exec|script...: 本進程在主進程被殺死之後,作業的stopped事件觸發之前執行。通常用來清理相關環境,例如刪除臨時的目錄。
Upstart主要有三種作業:
Task Job: 有確定的生命週期和終止狀態,例如刪除一個檔案。
Service Job: 長期啟動並執行進程,例如守護進程,通常不會自己終止。例如資料庫、web伺服器、ftp伺服器等。
Abstact Job: 沒有exec節或script節的作業,這樣的作業仍然可以被啟動和終止,但是不會被分配PID。這樣作業啟動後如果沒有被管理員終止,會永久的運行。抽象作業只存在於Upstart自己內部,但有時個它非常有用,例如定義“永久運行”的作業,用來同步等。
作業的10種狀態:
waiting: 初始狀態。
starting: 作業開始啟動。
pre-start: 運行pre-start配置節。
spawned: 運行script或exec節。
post-start: 運行post-start節。
running: 運行完post-start節之後的臨時狀態,表示作業正在運行(但可能沒有關聯的PID)。
pre-stop:運行pre-stop節。
stopping:運行完pre-stop節之後的臨時狀態。
killed: 作業要被終止。
post-stop: 運行post-stop節。
作業的狀態通過inictl status命令輸出的中status域來顯示給使用者。下面是合法的作業狀態轉移:
目前狀態 目標
start stop
waiting starting n/a
starting pre-start stopping
pre-start spawned stopping
spawned post-start stopping
post-start running stopping
running stopping pre-stop或stopping
pre-stop running stopping
stopping killed killed
killed post-stop post-stop
post-stop starting waiting
例如,如果作業的目前狀態是starting,且它的目標是start,則它將轉移到pre-start狀態。注意作業的狀態改變可能會非常快以致於你看不到initctl輸出中的相關值。你可以通過把log-priority設為debug或info層級來詳細選項組的改變,更多細節參考initctl log-priority命令。通過啟動、終止、重啟一個作業或觸發一個事件可以引起作業狀態的轉移。可以用"tail -f"命令查看系統log檔案。
(2)事件
作業可以由管理員通過start和stop工具來啟動或停止,也可以通過Upstart觸發某種事件來啟動或終止。Upstart要求您更新初始化設定檔來支援基於事件的操作模式。管理員可能使用initctl emit <event> 來建立一個事件,事件名可以和作業名相同。與事件相關的配置節主要有:
start on EVENT [[KEY=]VALUE]... [and|or...]: 定義能導致作業自動啟動的事件集。KEY和VALUE指定環境變數及其值。例如:
start on started gdm or started kdm
start on device-added SUBSYSTEM=tty DEVPATH=ttyS*
start on net-device-added INTERFACE!=lo
stop on EVENT [[KEY=]VALUE]... [and|or...]: 定義導致作業自動停止的事件集。文法與start on類似。
您可以建立新事件。例如,要建立一個名為myevent的任意事件,並使用echo命令表示該事件的接收,請使用下面這個簡短的作業:
start on myeventexec echo myevent receivedconsole output
這段代碼指定在接收到myevent事件時將觸發該作業。然後代碼執行指定的操作(向控制台發出文本)。使用upstart配置(/etc/init/)中給出的檔案,可以使用initctl工具觸發它:
initctl emit myevent。
upstart使用的作業設定檔的工作方式類似與傳統的rc init檔案,它們是基於非同步事件自發操作的。清單3提供了一個簡單的範例配置,它可以接收3個事件: startup啟動作業;shutdown和 runlevel-3,停止作業。shell 執行作業的 script 部分的內容(使用 -e 選項來結束出錯指令碼)。
清單 3. sysvinit rc 2 指令碼的簡化 upstart 指令碼
start on startupstop on shutdownstop on runlevel-3scriptset $(runlevel --set 2 || true)exec /etc/init.d/rc 2end script
initctl 工具提供了類似於 telinit 的功能,不過增加了一些特定於 upstart 的特性。initctl用來啟動或終止作業、列表作業、以及擷取作業的狀態、發出事件、重啟 init 進程,等等。正如您前面看到的一樣,您可以使用 initctl 和 emit 選項為 upstart 產生一個事件。list 選項讓您可以通過標識作業狀態來深入瞭解系統操作。它告訴您目前正在等待哪些服務,以及哪些服務目前是活動的。initctl 工具還可以顯示用於調試而接收的事件。
Upstart的事件數目量是沒有限制的,但init守護進程和telinit工具定義了一組常用的標準事件。主要有:
starting: 當作業被調度並開始運行時,由Upstart觸發。
started: 當作業正在運行時被觸發。
stopping: 當作業開始終止時被觸發。
stopped: 當作業已經完成時(成功或失敗)被觸發。
當upstart init進程啟動時,它會發出startup事件,這將啟用實現了System V相容性的事件和runlevel事件。隨著作業的啟動和停止,init守護進程將觸發starting, started, stopping, stopped事件。另一個核心事件shutdown則是在系統關閉時發出的。其他核心事件包括ctrlaltdel,它說明您按下了Ctrl-Alt-Delete,或kbdrequest,它用來說明您按下了 Alt-Up(向上箭頭)鍵組合。
以Upstart自己的啟動過程為例,其流程如下(Ubuntu中):
1)Upstart執行內部初始化。
2)Upstart自己觸發一個單一的稱為startup的事件。這個事件觸發其餘的系統初始化過程。注意沒有"startup"作業(因此沒有/etc/init/startup.conf檔案)。
3)init運行mountall作業(/etc/init/mountall.conf),因為mountall作業中有"start on startup"配置節,滿足觸發後啟動並執行條件。
4)mountall作業依次觸發一系列的事件,包括local-filesystems, all-swap等。
下面是mountall.conf的內容:
# mountall - Mount filesystems on boot## This helper mounts filesystems in the correct order as the devices# and mountpoints become available.description"Mount filesystems on boot"start on startupstop on starting rcSexpect daemontaskemits virtual-filesystemsemits local-filesystemsemits remote-filesystemsemits all-swapsemits filesystememits mountingemits mounted# temporary, until we have progress indication# and output capture (next week :p)console outputscript . /etc/default/rcS [ -f /forcefsck ] && force_fsck="--force-fsck" [ "$FSCKFIX" = "yes" ] && fsck_fix="--fsck-fix" # set $LANG so that messages appearing in plymouth are translated if [ -r /etc/default/locale ]; then . /etc/default/locale export LANG LANGUAGE LC_MESSAGES elif [ -r /etc/environment ]; then . /etc/environment export LANG LANGUAGE LC_MESSAGES fi exec mountall --daemon $force_fsck $fsck_fixend scriptpost-stop script rm -f /forcefsck 2>dev/null || trueend script
從內容可以看出,mountall作業用來以正確的順序掛載檔案系統。在mountall作業執行完之後,由於觸發了filesystem事件,Upstart接著會去執行rc-sysinit作業,它負責產生System v相容的runlevel事件。前面我們已經分析過,Ubuntu在rc-sysinit作業中處理inittab並切換到initdefault層級來運行(Fedora是在rcS作業中完成)。
Upstart有三種事件類型:
Signal Event: 非阻塞的,即非同步。觸發訊號型事件會立即返回,調用者繼續往下執行。訊號型的意思就是廣播者並不關心誰會接收它,也不需要等待是否發生某種事情,它只是用來提供資訊,用作通訊。使用帶--no-wait選項的initctl emit命令來建立訊號型事件。例如initctl emit --no-wait mysignal。注意事件觸發的非阻塞特性並不會直接影響那些與此事件有關的作業,它只是影響觸發者程式,允許其繼續執行,而無需等待任何使用這個事件的作業。作業本身的非阻塞特性則會影響作業自己,它使得作業不能被終止或延遲,不能以任何形式持有觸發者的操作。
Method Event: 阻塞的,即同步的。它通常與Task job結合使用。方法型事件的行為類似於程式設計語言中的method或function call,調用者需要等待這個工作的完成。例如initctl emit mymethod,這個方法型事件被同步地觸發,調用者需要等待直到initctl命令完成。在mymethod事件上啟動的任務可能運行成功,也可能失敗,假設有一個作業/etc/init/myapp.conf,如下:
start on mymethodtaskexec /usr/bin/myapp $ACTION
你應該啟動myapp作業,並檢查這個“方法”是否正常完成:
# inictl emit mymethod ACTION=do_something
[ $? -ne 0 ] && { echo "ERROR: myapp failed"; exit1; }
Hook Event: 阻塞的,即同步的。鉤子介於訊號和方法之間。它是一種通知,表示系統發生了一些改變,不同於訊號,鉤子型事件的觸發者需要等待作業的完成。因此鉤子通常用來標誌即將發生的改變一些事情。starting和stopping是鉤子型事件,被Upstart觸發以表明作業即將啟動或即將終止。
注意事件與狀態是有區別的,雖然Upstart內部使用狀態(這些狀態可以通過initctl status和list命令顯示給使用者看),但事件是設定檔指定作業期望行為的一種方式,starting, started, stopping, stopped是事件,不是狀態。這些事件在一些特殊的狀態轉移發生之前觸發。例如,starting事件在與此事件相關的作業實際進行運行隊列之前被觸發。
(3)作業環境:
env KEY=VALUE: 定義一個預設的環境變數,其值可以被啟動作業的事件或命令覆蓋。
export KEY: 把環境變數的值匯出到starting, started, stopping, stopped事件中。
(4)服務、任務和重生(respawning)
預設情況下,作業是一個服務,這意味著當作業正在運行時,啟動作業的動作就被認為執行完了,當事件以0狀態代碼退出時,服務將重新啟動。
task: 這個配置節指定作業是一個任務,而不是服務。這意味著啟動作業的動作直到作業本身已經運行或停止了,才算是完成,當事件以0狀態代碼退出時,表示任務執行完成,不再重啟。注意start命令和任何starting, stopping事件將被阻塞,直到一個服務正在運行或一個任務已經完成。
respawn: 這個配置節表示服務或任務如果非正常終止,將自動啟動。除stop命令外的終止都是非正常的終止。
(5)執行個體
預設情況下,在同一時間任何作業只允許有一個執行個體存在,嘗試再次啟動一個正在啟動並執行作業將導致錯誤。通過用配置節instance NAME定義不同的執行個體名,可以允許作業有多個執行個體存在。只要正在啟動並執行執行個體中沒有同名的,就允許啟動這個執行個體。例如:
instance $CONFFILEexec /sbin/httpd -c $CONFFILEinstance $TTYexec /sbin/getty -8 38300 $TTY
(6)文檔和外部工具相關
主要的配置節有description, author, version, emits。
(7)進程環境
主要的配置節有console, umask, nice, oom, chroot, chdir, limit等。
console output|owner: 作業的標準輸入、輸出和錯誤檔案描述符預設是串連到/dev/null的,指定本節則將它們串連到系統控制台/dev/console。若設定了owner,還會作業設為系統控制台的owner,這意味著當按下某些按鍵組合如Ctrl+C時,作業會從核心中接收到相關的訊號。
umask UMASK: 設定檔案模式掩碼。
(8)作業生命週期
啟動一個作業的流程:
1)Upstart把作業的目標從stop改成start。正如目標的名字指示的一樣,作業(執行個體)現在嘗試啟動。目標可以用initctl list和status命令顯示。
2)Upstart觸發starting事件,指示作業即將啟動。這個事件包括兩個環境變數:JOB指定作業名;INSTANCE指定執行個體名,如果啟動單一的執行個體(沒有instance配置節),則執行個體名為空白。
3)starting事件完成。
4)如果pre-start節存在,則產生pre-start進程。如果pre-start失敗,Upstart把目標從start改成stop,設定表示失敗的變數並觸發stopping和stopped事件。
5)Upstart產生主進程。即運行script或exec配置節,如果沒有script或exec配置節,則Upstart什麼也不做。
6)Upstart確定作業的最終PID,可參考expect fork和expect守護r進程。
7)如果post-start配置節存在,則產生post-start進程。。如果post-start失敗,Upstart把目標從start改成stop,設定表示失敗的變數並觸發stopping和stopped事件。
8)Upstart觸發started事件。這個事件包含與starting同樣的環境變數。對Service job,當started事件完成後,主進程即完全地運行起來了。如果是Task job,則任務執行完成(成功或失敗)。
終止一個作業的流程:
1)Upstart把作業的目標從start改為stop。現在作業(執行個體)嘗試終止。
2)如果pre-stop配置節存在,則產生pre-stop進程。如果pre-stop失敗,Upstart設定表示失敗的變數,並觸發stopping和stopped事件。
3)如果作業有script或exec配置節,則終止主進程,首先向主進程發送SIGTERM訊號,然後Upstart等待kill timeout秒數(預設為5秒),如果進程仍然在運行,則向進程發送SIGKILL訊號,因為進程不能選擇忽略此訊號,因此能保證進程被終止。
4)Upstart觸發stopping事件。這個事件有一系列的相關環境變數,包括:
JOB: 與本事件關聯的作業名。
INSTANCE: 執行個體名。
RESULT: "ok"表示作業正常退出,"failed"表示作業失敗,注意退出結果的顯示可以用normal exit配置節修改。
PROCESS: 導致作業失敗的配置節名稱。如果RESULT=ok,則本變數不會被設定。如果設定了,可能值有pre-start, post-start, main(表示script或exec配置節), pre-stop, post-stop, respawn(表示作業產生次數超過了respawn limit配置節設定的限制)。
EXIT_STATUS或EXIT_SIGNAL: 如果作業自己退出則設定EXIT_STATUS,如果由於接收到訊號退出則設定EXIT_SIGNAL。如果兩個變數都沒有設定,則進程在產生的過程中出現了問題(例如指定要啟動並執行命令沒有找到)。
5)如果post-stop配置節存在,則產生post-stop進程。如果post-start失敗,Upstart設定表示失敗的變數並觸發stopped事件。
6)Upstart觸發stopped事件。當stopped事件完成後,作業即完全終止。stopped事件與stopping事件有相同的環境變數集。
(9)運行層級
Debian/Ubuntu系統的運行層級如下:
0:系統關閉。
1:單一使用者模式。
2:多使用者的包括網路的圖形介面模式(預設)。
3:與2相同,但未使用。
4:與2相同,但未使用。
5:與2相同,但未使用。
6:系統重啟。
還有兩個偽運行層級:
N:以前的運行層級不能確定。
S:單一使用者模式的別名。
顯示運行層級可用runlevel命令,改變運行層級可用reboot, shutdown或telinit。改變預設運行層級可修改rc-sysinit.conf中的DEFAULT_RUNLEVEL變數的值,或在核心啟動命令列中加入
DEFAULT_RUNLEVEL=1之類的參數。 傳統上,預設運行層級被編碼在/etc/inittab中,然而這個檔案在Upstart中不再使用(但是為了向後相容,Upstart仍然支援它)。
(10)系統啟動
在前面我們詳細地分析過init啟動系統的過程(以Fedora為例子),這裡我們以Ubuntu為例子,並從Upstart的視角來闡述。在系統引導時,當initramfs檔案系統運行起來時(用於設定RAID、解鎖加密的檔案系統卷等),將會運行/sbin/init並分配PID為1,這樣Upstart接過控制權。在預設運行層級2上的啟動流程如下:
1)Upstart執行內部的初始化。
2)Upstart觸發一個單一的稱為startup的事件,這個事件觸發其餘的系統初始化過程。
3)init運行一些指定了start on startup的作業。這其中最著名的就是mountall作業,用來掛載硬碟和檔案系統。
4)mountall作業依次觸發一系列的事件,包括local-filesystems, virtual-filesystems, all-swaps等。當系統裝置和掛載點可用時,它運行mountall精靈來完成掛載硬碟和檔案系統的工作。
5)virtual-filesystems事件引發udev作業啟動。它運行uded精靈來管理系統的裝置,並監控裝置的改變。
6)udev作業引發upstart-udev-bridge作業啟動。
7)upstart-udev-bridge作業將會在某個點處觸發"net-device-up IFACE=lo"事件,以表示本網(例如IPv4的127.0.0.0)可用。
8)在最終的檔案系統掛載之後,mountall將會觸發filesystem事件。
9)由於rc-sysinit作業中有start on filesystem and net-device-up IFACE=lo節,Upstart將會啟動rc-sysinit作業(Fedora中沒有這個作業,而是仍然由傳統的rc-sysinit指令碼完成工作)。
10)rc-sysinit作業最後調用telinit命令,格式為 telinit "${DEFAULT_RUNLEVEL}"。
11)telinit命令觸發runlevel事件,即執行runlevel RUNLEVEL=2 PREVLEVEL=N。注意這就是telinit所做的全部工作,它自己並不會切換運行層級,而通過runlevel程式實現。
12)runlevel事件引發很多其他的Upstart作業啟動,包括/etc/init/rc.conf,它用來啟動遺留的SystemV init系統。
(11)系統關閉
在系統關閉過程中,有一些重要的事實需要知道:
1)Upstart決不會關閉自己。Upstart會在系統斷電時終止,如果它之前終止過,說明是一個bug。
2)Upstart決不會終止沒有stop on配置節的作業。
3)Ubuntu既使用Upstart作業,也使用SysV作業。核心的服務由Upstart處理,一些額外的服務可以在遺留的SystemV模式下運行。這主要是為向後相容,因此在Ubuntu的Universe和Mutiverse軟體庫中有大量的軟體包,為避免更改每個軟體包以使它能在Upstart下工作,Upstart允許使用已經存在的SystemV(還包括Debian相容的)指令碼。
關閉系統需要先執行關機動作,例如在圖形化使用者介面中單擊"Shut Down...",運行命令shutdown -h now等。關機的流程如下:
1)假設當前運行層級為2,關機動作將會使Upstart觸發runlevel事件,即 runlevel RUNLEVEL=0 PREVLEVEL=2。
2)作業/etc/init/rc.conf將被運行。這個作業調用/etc/init.d/rc,並傳遞新的運行層級(“0“)。
3)SystemV系統調用/etc/rc0.d/中必要的指令碼(都是指向/etc/init.d/中指令碼的連結),來終止SystemV服務。
4)其中有一個/etc/init.d/sendsigs指令碼,這個指令碼中有個do_stop()函數,它負責殺死所有沒有被終止的進程(包括Upstart進程)。
(12)系統重啟
先要執行重啟動作,例如在圖形介面中單擊"Restart...",運行shutdown-r now 或reboot。重啟的流程如下:
1)假設當前運行層級為2,重啟動作將會使Upstart觸發runlevel事件,即 runlevel RUNLEVEL=6 PREVLEVEL=2。
2)作業/etc/init/rc.conf將被運行。這個作業調用/etc/init.d/rc,並傳遞新的運行層級(“6“)。
3)SystemV系統調用/etc/rc6.d/中必要的指令碼(都是指向/etc/init.d/中指令碼的連結),來終止SystemV服務。
4)其中有一個/etc/init.d/sendsigs指令碼,這個指令碼中有個do_stop()函數,它負責殺死所有沒有被終止的進程(包括Upstart進程)。
(13)復原模式
Ubuntu提供了復原模式以應對系統出現問題的情況。這由friendly-recovery軟體包來處理。
總的來說,Upstart是傳統init的一個有趣的替代程式,並且具有一些獨特的優點。實際上已經不存在什麼理由再使用運行層級了,因為系統將充分利用硬體進行引導。任何沒有給出的硬體都不會觸發需要它的任務。Upstart 也可以很好地處理熱插拔裝置。例如,如果在完成系統引導很長時間以後插入了一塊 PCMCIA 網卡,那就會產生 network-interface-added 事件。這個事件會引起動態主機設定通訊協定(Dynamic Host Configuration Protocol,DHCP)作業來對這個網卡進行配置,產生一個
network-interface-up事件。當為這個新介面分配一個預設路由時,會產生一個 default-route-up 事件。此時,需要網路介面的作業(例如郵件伺服器或 Web 服務器)就可以自動啟動(如果介面消失,這些服務將會自動停止)。
編譯和安裝 upstart 非常簡單,並且遵循典型的 configure、make 和 make install 模式。 Upstart 提供了一組樣本作業,它們與典型的 init 配置相容。與 initng 類似,新應用程式必須要根據需求編寫自己的作業(可能還需要添加新事件)。不管怎樣,部署新的 init 系統都會有一些風險。不過 upstart 的優點當然值得去冒這些風險並執行其他必要的操作。
正如上面介紹的一樣,initctl 工具提供了人們對 telinit 所期望的功能。不過 initctl 也為跟蹤和調試提供了附加功能。