Sysvinit, UpStart, Systemd

Sysvinit, UpStart, Systemd

什麼是 Init 系統,init 系統的歷史和現狀

  • Linux 操作系統的啟動首先從 BIOS 開始,接下來進入 boot loader,由 bootloader 載入內核,進行內核初始化。內核初始化的最後一步就是啟動 pid 為 1 的 init 進程。這個進程是系統的第一個進程。它負責產生其他所有用戶進程。
  • init 以守護進程方式存在,是所有其他進程的祖先。init 進程非常獨特,能夠完成其他進程無法完成的任務。
    • Init 系統能夠定義、管理和控制 init 進程的行為。它負責組織和運行許多獨立的或相關的始化工作(因此被稱為 init 系統),從而讓計算機系統進入某種用戶預訂的運行模式。
    • 大多數 Linux 發行版的 init 系統是和 System V 相兼容的,被稱為 sysvinit。這是人們最熟悉的 init 系統。
    • 其他的發行版如 Gentoo 是自己定製的。Ubuntu 和 RHEL 採用 upstart 替代了傳統的 sysvinit。
    • 而 Fedora 從版本 15 開始使用了一個被稱為 systemd 的新 init 系統。
  • 在 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 以便執行一些重要的系統初始化任務。在 RedHat 公司的 RHEL5 中(RHEL6 已經使用 upstart 了),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 目錄下。如下所示:
    • 清單 1.rc5.d 目錄下的腳本
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      [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 的優點是概念簡單。Service 開發人員只需要編寫啟動和停止腳本,概念非常清楚;將 service 添加/刪除到某個 runlevel 時,只需要執行一些創建/刪除軟連接文件的基本操作;這些都不需要學習額外的知識或特殊的定義語法(UpStart 和 Systemd 都需要用戶學習新的定義系統初始化行為的語言)。
  • 其次,sysvinit 的另一個重要優點是確定的執行順序:腳本嚴格按照啟動數字的大小順序執行,一個執行完畢再執行下一個,這非常有益於錯誤排查。UpStart 和 systemd 支持並發啟動,導致沒有人可以確定地瞭解具體的啟動順序,排錯不易。
  • 但是串行地執行腳本導致 sysvinit 運行效率較慢,在新的 IT 環境下,啟動快慢成為一個重要問題。此外動態設備加載等 Linux 新特性也暴露出 sysvinit 設計的一些問題。針對這些問題,人們開始想辦法改進 sysvinit,以便加快啟動時間,並解決 sysvinit 自身的設計問題。
  • Upstart 是第一個被廣泛應用的新一代 init 系統。我們在接下來的第二部分介紹 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 主要的概念是 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)。

      狀態名 含義
      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。

    • 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
    2
    3
    4
    5
    6
    7
    8
    #This is a simple demo of Job Configure file
    #This line is comment, start with #

    #Stanza 1, The author
    author 「Liu Ming」

    #Stanza 2, Description
    description 「This job only has author and description, so no use, just a demo」

Stanza

  • 比較重要的小節有以下幾個
  • “expect” Stanza

    • Upstart 除了負責系統的啟動過程之外,和 SysVinit 一樣,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”關鍵字定義需要運行的腳本。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # mountall.conf
      description 「Mount filesystems on boot」
      start on startup
      stop on starting rcS
      ...
      script
      . /etc/default/rcS
      [ -f /forcefsck ] && force_fsck=」--force-fsck」
      [ 「$FSCKFIX」=」yes」 ] && fsck_fix=」--fsck-fix」

      ...

      exec mountall –daemon $force_fsck $fsck_fix
      end script
      ...
      • 這是 mountall 的例子,該工作在系統啟動時運行,負責掛載所有的文件系統。該工作需要執行複雜的腳本,由”script”關鍵字定義;在腳本中,使用了 exec 來執行 mountall 命令。
  • “start on” Stanza 和 “stop on” Stanza

    • “start on”定義了觸發工作的所有事件。
    • “start on”的語法很簡單,如下所示:
      1
      start on EVENT [[KEY=]VALUE]… [and|or…]
      • EVENT 表示事件的名字,可以在 start on 中指定多個事件,表示該工作的開始需要依賴多個事件發生。
      • 多個事件之間可以用 and 或者 or 組合,”表示全部都必須發生”或者”其中之一發生即可”等不同的依賴條件。
      • 除了事件發生之外,工作的啟動還可以依賴特定的條件,因此在 start on 的 EVENT 之後,可以用 KEY=VALUE 來表示額外的條件,一般是某個環境變量(KEY)和特定值(VALUE)進行比較。如果只有一個變量,或者變量的順序已知,則 KEY 可以省略。
  • “stop on”和”start on”非常類似,只不過是定義工作在什麼情況下需要停止。

    1
    2
    3
    4
    5
    6
    #dbus.conf
    description 「D-Bus system message bus」

    start on local-filesystems
    stop on deconfiguring-networking

    • 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。
  • 傳統 desktop session 啟動過程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    init
    |- lightdm
    | |- Xorg
    | |- lightdm ---session-child
    | |- gnome-session --session=ubuntu
    | |- compiz
    | |- gwibber
    | |- nautilus
    | |- nm-applet
    | :
    | :
    |
    |- dbus-daemon --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 的 Desktop session init 過程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    init
    |- lightdm
    | |- Xorg
    | |- lightdm ---session-child
    | |- session-init # <-- upstart running as normal user
    | |- dbus-daemon --session
    | |- gnome-session --session=ubuntu
    | |- compiz
    | |- gwibber
    | |- nautilus
    | |- nm-applet
    | :
    | :
    :
    :

    UpStart 使用

  • 有兩種人員需要瞭解 Upstart 的使用。

    • 第一類是系統開發人員,比如 MySQL 的開發人員。它們需要瞭解如何編寫工作配置文件,以便用 UpStart 來管理服務。比如啟動,停止 MySQL 服務。
        • 系統開發人員不僅需要掌握工作配置文件的寫法,還需要瞭解一些針對服務進程編程上的要求。
    • 另外一種情況是系統管理員,它們需要掌握 Upstart 的管理命令以便配置和管理系統的初始化,管理系統服務。

Upstart 系統中的運行級別

  • Upstart 的運作完全是基於工作和事件的。
  • 工作的狀態變化和運行會引起事件,進而觸發其它工作和事件。
  • 而傳統的 Linux 系統初始化是基於運行級別的,即 SysVInit。因為歷史的原因,Linux 上的多數軟件還是採用傳統的 SysVInit 腳本啟動方式,並沒有為 UpStart 開發新的啟動腳本,因此即便在 Debian 和 Ubuntu 系統上,還是必須模擬老的 SysVInit 的運行級別模式,以便和多數現有軟件兼容。
  • 雖然 Upstart 本身並沒有運行級別的概念,但完全可以用 UpStart 的工作模擬出來。讓我們完整地考察一下 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 條件如下:
    • start on filesystem and net-device-up IFACE=lo
  • 任務 rc-sysinit 調用 telinit。Telinit 任務會發出 runlevel 事件,觸發執行/etc/init/rc.conf。
  • rc.conf 執行/etc/rc$.d/目錄下的所有腳本,和 SysVInit 非常類似,讀者可以參考本文第一部分的描述。

程序開發時需要注意的事項

  • 作為程序開發人員,在編寫系統服務時,需要瞭解 UpStart 的一些特殊要求。只有符合這些要求的軟件才可以被 UpStart 管理。
  • 規則一,派生次數需聲明。

    • 很多 Linux 後台服務都通過派生兩次的技巧將自己變成後台服務程序。
    • 如果您編寫的服務也採用了這個技術,就必須通過文檔或其它的某種方式明確地讓 UpStart 的維護人員知道這一點,這將影響 UpStart 的 expect 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,這是一個帶子命令風格的命令行工具。

    SERVICE 命令 UPSTART INITCTL 命令
    service list initctl list
    service start initctl start
    service stop initctl stop
    service restart initctl restart
    service reload initctl reload
  • 很多情況下管理員並不喜歡子命令風格,因為需要手動鍵入的字符太多。UpStart 還提供了一些快捷命令來簡化 initctl,實際上這些命令只是在內部調用相應的 initctl 命令。

    • 比如 reload,restart,start,stop 等等。啟動一個服務可以簡單地調用
    • start <job> = initctl start <job>
  • 一些命令是為了兼容其它系統(主要是 sysvinit),比如顯示 runlevel 用/sbin/runlevel 命令:
    1
    2
    $runlevel
    N 2
    • 這個輸出說明當前系統的運行級別為 2。而且系統沒有之前的運行級別,也就是說在系統上電啟動進入預定運行級別之後沒有再修改過運行級別。
    • 在 Upstart 系統中,需要修改/etc/init/rc-sysinti.conf 中的 DEFAULT_RUNLEVEL 這個參數,以便修改默認啟動運行級別。這一點和 sysvinit 的習慣有所不同,大家需要格外留意。
  • 還有一些隨 UpStart 發佈的小工具,用來幫助開發 UpStart 或者診斷 UpStart 的問題
    • 比如 init-checkconf 和 upstart-monitor
    • 還可以使用 initctl 的 emit 命令從命令行發送一個事件。
      • initctl emit <event>
      • 這一般是用於 UpStart 本身的排錯。

        Upstart 小結

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

Systemd

簡介和特點

  • Systemd 是 Linux 系統中最新的初始化系統(init),它主要的設計目標是克服 sysvinit 固有的缺點,提高系統的啟動速度。systemd 和 ubuntu 的 upstart 是競爭對手,預計會取代 UpStart,實際上在作者寫作本文時,已經有消息稱 Ubuntu 也將採用 systemd 作為其標準的系統初始化系統。

  • Systemd 的很多概念來源於蘋果 Mac OS 操作系統上的 launchd,不過 launchd 專用於蘋果系統,因此長期未能獲得應有的廣泛關注。Systemd 借鑑了很多 launchd 的思想,它的重要特性如下:

    • 同 SysVinit 和 LSB init scripts 兼容

      • Systemd 是一個”新來的”,Linux 上的很多應用程序並沒有來得及為它做相應的改變。和 UpStart 一樣,systemd 引入了新的配置方式,對應用程序的開發也有一些新的要求。
      • 如果 systemd 想替代目前正在運行的初始化系統,就必須和現有程序兼容。任何一個 Linux 發行版都很難為了採用 systemd 而在短時間內將所有的服務代碼都修改一遍。
      • Systemd 提供了和 Sysvinit 以及 LSB initscripts 兼容的特性。系統中已經存在的服務和進程無需修改。這降低了系統向 systemd 遷移的成本,使得 systemd 替換現有初始化系統成為可能。
    • 更快的啟動速度

      • Systemd 提供了比 UpStart 更激進的並行啟動能力,採用了 socket / D-Bus activation 等技術啟動服務。一個顯而易見的結果就是:更快的啟動速度。
      • 為了減少系統啟動時間,systemd 的目標是:
        • 儘可能啟動更少的進程
        • 儘可能將更多進程並行啟動
      • 同樣地,UpStart 也試圖實現這兩個目標。UpStart 採用事件驅動機制,服務可以暫不啟動,當需要的時候才通過事件觸發其啟動,這符闔第一個設計目標;此外,不相干的服務可以並行啟動,這也實現了第二個目標。
      • 假設有 7 個不同的啟動項目, 比如 JobA、Job B 等等。在 SysVInit 中,每一個啟動項目都由一個獨立的腳本負責,它們由 sysVinit 順序地,串行地調用。因此總的啟動時間為 T1+T2+T3+T4+T5+T6+T7。其中一些任務有依賴關係,比如 A,B,C,D。
      • 而 Job E 和 F 卻和 A,B,C,D 無關。這種情況下,UpStart 能夠並發地運行任務{E,F,(A,B,C,D)},使得總的啟動時間減少為 T1+T2+T3。
      • 這無疑增加了系統啟動的並行性,從而提高了系統啟動速度。但是在 UpStart 中,有依賴關係的服務還是必須先後啟動。比如任務 A,B,(C,D)因為存在依賴關係,所以在這個局部,還是串行執行。
      • 讓我們例舉一些例子, Avahi 服務需要 D-Bus 提供的功能,因此 Avahi 的啟動依賴於 D-Bus,UpStart 中,Avahi 必須等到 D-Bus 啟動就緒之後才開始啟動。類似的,livirtd 和 X11 都需要 HAL 服務先啟動,而所有這些服務都需要 syslog 服務記錄日誌,因此它們都必須等待 syslog 服務先啟動起來。然而 httpd 和他們都沒有關係,因此 httpd 可以和 Avahi 等服務並發啟動。
      • Systemd 能夠更進一步提高並發性,即便對於那些 UpStart 認為存在相互依賴而必須串行的服務,比如 Avahi 和 D-Bus 也可以並發啟動。
      • 所有的任務都同時並發執行,總的啟動時間被進一步降低為 T1。
      • 可見 systemd 比 UpStart 更進一步提高了並行啟動能力,極大地加速了系統啟動時間。
    • systemd 提供按需啟動能力

      • 當 sysvinit 系統初始化的時候,它會將所有可能用到的後台服務進程全部啟動運行。並且系統必須等待所有的服務都啟動就緒之後,才允許用戶登錄。這種做法有兩個缺點:首先是啟動時間過長;其次是系統資源浪費。
      • 某些服務很可能在很長一段時間內,甚至整個服務器運行期間都沒有被使用過。比如 CUPS,打印服務在多數服務器上很少被真正使用到。您可能沒有想到,在很多服務器上 SSHD 也是很少被真正訪問到的。花費在啟動這些服務上的時間是不必要的;同樣,花費在這些服務上的系統資源也是一種浪費。
      • Systemd 可以提供按需啟動的能力,只有在某個服務被真正請求的時候才啟動它。當該服務結束,systemd 可以關閉它,等待下次需要時再次啟動它。
    • Systemd 採用 Linux 的 Cgroup 特性跟蹤和管理進程的生命週期

      • init 系統的一個重要職責就是負責跟蹤和管理服務進程的生命週期。它不僅可以啟動一個服務,也必須也能夠停止服務。這看上去沒有什麼特別的,然而在真正用代碼實現的時候,您或許會發現停止服務比一開始想的要困難。
      • 服務進程一般都會作為精靈進程(daemon)在後台運行,為此服務程序有時候會派生(fork)兩次。在 UpStart 中,需要在配置文件中正確地配置 expect 小節。這樣 UpStart 通過對 fork 系統調用進行計數,從而獲知真正的精靈進程的 PID 號。
      • 如果 UpStart 找錯了,將 p1 作為服務進程的 Pid,那麼停止服務的時候,UpStart 會試圖殺死 p1進程,而真正的 p1“進程則繼續執行。換句話說該服務就失去控制了。
      • 還有更加特殊的情況。比如,一個 CGI 程序會派生兩次,從而脫離了和 Apache 的父子關係。當 Apache 進程被停止後,該 CGI 程序還在繼續運行。而我們希望服務停止後,所有由它所啟動的相關進程也被停止。
      • 為了處理這類問題,UpStart 通過 strace 來跟蹤 fork、exit 等系統調用,但是這種方法很笨拙,且缺乏可擴展性。systemd 則利用了 Linux 內核的特性即 CGroup 來完成跟蹤的任務。當停止服務時,通過查詢 CGroup,systemd 可以確保找到所有的相關進程,從而乾淨地停止服務。
      • CGroup 已經出現了很久,它主要用來實現系統資源配額管理。CGroup 提供了類似文件系統的接口,使用方便。當進程創建子進程時,子進程會繼承父進程的 CGroup。因此無論服務如何啟動新的子進程,所有的這些相關進程都會屬於同一個 CGroup,systemd 只需要簡單地遍歷指定的 CGroup 即可正確地找到所有的相關進程,將它們一一停止即可。
    • 啟動掛載點和自動掛載的管理

      • 傳統的 Linux 系統中,用戶可以用/etc/fstab 文件來維護固定的文件系統掛載點。這些掛載點在系統啟動過程中被自動掛載,一旦啟動過程結束,這些掛載點就會確保存在。這些掛載點都是對系統運行至關重要的文件系統,比如 HOME 目錄。和 sysvinit 一樣,Systemd 管理這些掛載點,以便能夠在系統啟動時自動掛載它們。Systemd 還兼容/etc/fstab 文件,您可以繼續使用該文件管理掛載點。
      • 有時候用戶還需要動態掛載點,比如打算訪問 DVD 內容時,才臨時執行掛載以便訪問其中的內容,而不訪問光盤時該掛載點被取消(umount),以便節約資源。傳統地,人們依賴 autofs 服務來實現這種功能。
      • Systemd 內建了自動掛載服務,無需另外安裝 autofs 服務,可以直接使用 systemd 提供的自動掛載管理能力來實現 autofs 的功能。
    • 實現事務性依賴關係管理

      • 系統啟動過程是由很多的獨立工作共同組成的,這些工作之間可能存在依賴關係,比如掛載一個 NFS 文件系統必須依賴網絡能夠正常工作。Systemd 雖然能夠最大限度地並發執行很多有依賴關係的工作,但是類似”掛載 NFS”和”啟動網絡”這樣的工作還是存在天生的先後依賴關係,無法並發執行。對於這些任務,systemd 維護一個”事務一致性”的概念,保證所有相關的服務都可以正常啟動而不會出現互相依賴,以至於死鎖的情況。
    • 能夠對系統進行快照和恢復

      • systemd 支持按需啟動,因此系統的運行狀態是動態變化的,人們無法準確地知道系統當前運行了哪些服務。Systemd 快照提供了一種將當前系統運行狀態保存並恢復的能力。
      • 比如系統當前正運行服務 A 和 B,可以用 systemd 命令行對當前系統運行狀況創建快照。然後將進程 A 停止,或者做其他的任意的對系統的改變,比如啟動新的進程 C。在這些改變之後,運行 systemd 的快照恢覆命令,就可立即將系統恢復到快照時刻的狀態,即只有服務 A,B 在運行。一個可能的應用場景是調試:比如服務器出現一些異常,為了調試用戶將當前狀態保存為快照,然後可以進行任意的操作,比如停止服務等等。等調試結束,恢復快照即可。
      • 這個快照功能目前在 systemd 中並不完善,似乎開發人員也沒有特別關注它,因此有報告指出它還存在一些使用上的問題,使用時尚需慎重。
    • 日誌服務

      • systemd 自帶日誌服務 journald,該日誌服務的設計初衷是克服現有的 syslog 服務的缺點。比如:
        • syslog 不安全,消息的內容無法驗證。每一個本地進程都可以聲稱自己是 Apache PID 4711,而 syslog 也就相信並保存到磁盤上。
        • 數據沒有嚴格的格式,非常隨意。自動化的日誌分析器需要分析人類語言字符串來識別消息。一方面此類分析困難低效;此外日誌格式的變化會導致分析代碼需要更新甚至重寫。
      • Systemd Journal 用二進制格式保存所有日誌信息,用戶使用 journalctl 命令來查看日誌信息。無需自己編寫複雜脆弱的字符串分析處理程序。
      • Systemd Journal 的優點如下:
        • 簡單性:代碼少,依賴少,抽象開銷最小。
        • 零維護:日誌是除錯和監控系統的核心功能,因此它自己不能再產生問題。舉例說,自動管理磁盤空間,避免由於日誌的不斷產生而將磁盤空間耗盡。
        • 移植性:日誌文件應該在所有類型的 Linux 系統上可用,無論它使用的何種 CPU 或者字節序。
        • 性能:添加和瀏覽日誌 非常快。
        • 最小資源佔用:日誌 數據文件需要較小。
        • 統一化:各種不同的日誌存儲技術應該統一起來,將所有的可記錄事件保存在同一個數據存儲中。所以日誌內容的全局上下文都會被保存並且可供日後查詢。例如一條 固件記錄後通常會跟隨一條內核記錄,最終還會有一條用戶態記錄。重要的是當保存到硬盤上時這三者之間的關係不會丟失。Syslog 將不同的信息保存到不同的文件中,分析的時候很難確定哪些條目是相關的。
        • 擴展性:日誌的適用範圍很廣,從嵌入式設備到超級計算機集群都可以滿足需求。
        • 安全性:日誌 文件是可以驗證的,讓無法檢測的修改不再可能。

          Systemd 的基本概念

          單元的概念

  • 系統初始化需要做的事情非常多。需要啟動後台服務,比如啟動 SSHD 服務;需要做配置工作,比如掛載文件系統。這個過程中的每一步都被 systemd 抽象為一個配置單元,即 unit。

  • 可以認為一個服務是一個配置單元;一個掛載點是一個配置單元;一個交換分區的配置是一個配置單元;等等。systemd 將配置單元歸納為以下一些不同的類型。然而,systemd 正在快速發展,新功能不斷增加。所以配置單元類型可能在不久的將來繼續增加。

    • service :代表一個後台服務進程,比如 mysqld。這是最常用的一類。
    • socket :此類配置單元封裝系統和互聯網中的一個 套接字 。當下,systemd 支持流式、數據報和連續包的 AF_INET、AF_INET6、AF_UNIX socket 。每一個套接字配置單元都有一個相應的服務配置單元 。相應的服務在第一個”連接”進入套接字時就會啟動(例如:nscd.socket 在有新連接後便啟動 nscd.service)。
    • device :此類配置單元封裝一個存在於 Linux 設備樹中的設備。每一個使用 udev 規則標記的設備都將會在 systemd 中作為一個設備配置單元出現。
    • mount :此類配置單元封裝文件系統結構層次中的一個掛載點。Systemd 將對這個掛載點進行監控和管理。比如可以在啟動時自動將其掛載;可以在某些條件下自動卸載。Systemd 會將/etc/fstab 中的條目都轉換為掛載點,並在開機時處理。
    • automount :此類配置單元封裝系統結構層次中的一個自掛載點。每一個自掛載配置單元對應一個掛載配置單元 ,當該自動掛載點被訪問時,systemd 執行掛載點中定義的掛載行為。
    • swap: 和掛載配置單元類似,交換配置單元用來管理交換分區。用戶可以用交換配置單元來定義系統中的交換分區,可以讓這些交換分區在啟動時被激活。
    • target :此類配置單元為其他配置單元進行邏輯分組。它們本身實際上並不做什麼,只是引用其他配置單元而已。這樣便可以對配置單元做一個統一的控制。這樣就可以實現大家都已經非常熟悉的運行級別概念。比如想讓系統進入圖形化模式,需要運行許多服務和配置命令,這些操作都由一個個的配置單元表示,將所有這些配置單元 組合為一個目標(target),就表示需要將這些配置單元全部執行一遍以便進入目標所代表的系統運行狀態。 (例如:multi-user.target 相當於在傳統使用 SysV 的系統中運行級別 5)
    • timer:定時器配置單元用來定時觸發用戶定義的操作,這類配置單元取代了 atd、crond 等傳統的定時服務。
    • snapshot :與 target 配置單元相似,快照是一組配置單元。它保存了系統當前的運行狀態。
  • 每個配置單元都有一個對應的配置文件,系統管理員的任務就是編寫和維護這些不同的配置文件,比如一個 MySQL 服務對應一個 mysql.service 文件。這種配置文件的語法非常簡單,用戶不需要再編寫和維護複雜的系統 5 腳本了。

    依賴關係

  • 雖然 systemd 將大量的啟動工作解除了依賴,使得它們可以並發啟動。但還是存在有些任務,它們之間存在天生的依賴,不能用”套接字激活”(socket activation)、D-Bus activation 和 autofs 三大方法來解除依賴(三大方法詳情見後續描述)。

    • 比如:掛載必須等待掛載點在文件系統中被創建;掛載也必須等待相應的物理設備就緒。為瞭解決這類依賴問 題,systemd 的配置單元之間可以彼此定義依賴關係。
  • Systemd 用配置單元定義文件中的關鍵字來描述配置單元之間的依賴關係。比如:unit A 依賴 unit B,可以在 unit B 的定義中用”require A”來表示。這樣 systemd 就會保證先啟動 A 再啟動 B。

    Systemd 事務

  • Systemd 能保證事務完整性。Systemd 的事務概念和數據庫中的有所不同,主要是為了保證多個依賴的配置單元之間沒有環形引用。比如 unit A、B、C,假如它們的依賴關係為:

  • 存在循環依賴,那麼 systemd 將無法啟動任意一個服務。此時 systemd 將會嘗試解決這個問題,因為配置單元之間的依賴關係有兩種:required 是強依賴;want 則是弱依賴,systemd 將去掉 wants 關鍵字指定的依賴看看是否能打破循環。如果無法修復,systemd 會報錯。

  • Systemd 能夠自動檢測和修復這類配置錯誤,極大地減輕了管理員的排錯負擔。

    Target 和運行級別

  • systemd 用目標(target)替代了運行級別的概念,提供了更大的靈活性,如您可以繼承一個已有的目標,並添加其它服務,來創建自己的目標。下表列舉了 systemd 下的目標和常見 runlevel 的對應關係:

SYSVINIT 運行級別 SYSTEMD 目標 備註
0 runlevel0.target, poweroff.target 關閉系統。
1, s, single runlevel1.target, rescue.target 單用戶模式。
2, 4 runlevel2.target, runlevel4.target, multi-user.target 用戶定義/域特定運行級別。默認等同於 3。
3 runlevel3.target, multi-user.target 多用戶,非圖形化。用戶可以通過多個控制台或網絡登錄。
5 runlevel5.target, graphical.target 多用戶,圖形化。通常為所有運行級別 3 的服務外加圖形化登錄。
6 runlevel6.target, reboot.target 重啟
emergency emergency.target 緊急 Shell

Systemd 的並發啟動原理

  • 如前所述,在 Systemd 中,所有的服務都並發啟動,比如 Avahi、D-Bus、livirtd、X11、HAL 可以同時啟動。乍一看,這似乎有點兒問題,比如 Avahi 需要 syslog 的服務,Avahi 和 syslog 同時啟動,假設 Avahi 的啟動比較快,所以 syslog 還沒有準備好,可是 Avahi 又需要記錄日誌,這豈不是會出現問題?
  • Systemd 的開發人員仔細研究了服務之間相互依賴的本質問題,發現所謂依賴可以分為三個具體的類型,而每一個類型實際上都可以通過相應的技術解除依賴關係。

    並發啟動原理之一:解決 socket 依賴

  • 絕大多數的服務依賴是套接字依賴。比如服務 A 通過一個套接字端口 S1 提供自己的服務,其他的服務如果需要服務 A,則需要連接 S1。因此如果服務 A 尚未啟動,S1 就不存在,其他的服務就會得到啟動錯誤。所以傳統地,人們需要先啟動服務 A,等待它進入就緒狀態,再啟動其他需要它的服務。Systemd 認為,只要我們預先把 S1 建立好,那麼其他所有的服務就可以同時啟動而無需等待服務 A 來創建 S1 了。如果服務 A 尚未啟動,那麼其他進程向 S1 發送的服務請求實際上會被 Linux 操作系統緩存,其他進程會在這個請求的地方等待。一旦服務 A 啟動就緒,就可以立即處理緩存的請求,一切都開始正常運行。
  • 那麼服務如何使用由 init 進程創建的套接字呢?
    • Linux 操作系統有一個特性,當進程調用 fork 或者 exec 創建子進程之後,所有在父進程中被打開的文件句柄 (file descriptor) 都被子進程所繼承。
    • 套接字也是一種文件句柄,進程 A 可以創建一個套接字,此後當進程 A 調用 exec 啟動一個新的子進程時,只要確保該套接字的 close_on_exec 標誌位被清空,那麼新的子進程就可以繼承這個套接字。子進程看到的套接字和父進程創建的套接字是同一個系統套接字,就彷彿這個套接字是子進程自己創建的一樣,沒有任何區別。
    • 這個特性以前被一個叫做 inetd 的系統服務所利用。Inetd 進程會負責監控一些常用套接字端口,比如 Telnet,當該端口有連接請求時,inetd 才啟動 telnetd 進程,並把有連接的套接字傳遞給新的 telnetd 進程進行處理。
      • 這樣,當系統沒有 telnet 客戶端連接時,就不需要啟動 telnetd 進程。Inetd 可以代理很多的網絡服務,這樣就可以節約很多的系統負載和內存資源,只有當有真正的連接請求時才啟動相應服務,並把套接字傳遞給相應的服務進程。
    • 和 inetd 類似,systemd 是所有其他進程的父進程,它可以先建立所有需要的套接字,然後在調用 exec 的時候將該套接字傳遞給新的服務進程,而新進程直接使用該套接字進行服務即可。

      並發啟動原理之二:解決 D-Bus 依賴

  • D-Bus 是 desktop-bus 的簡稱,是一個低延遲、低開銷、高可用性的進程間通信機制。它越來越多地用於應用程序之間通信,也用於應用程序和操作系統內核之間的通信。很多現代的服務 進程都使用D-Bus 取代套接字作為進程間通信機制,對外提供服務。
    • 比如簡化 Linux 網絡配置的 NetworkManager 服務就使用 D-Bus 和其他的應用程序或者服務進行交互:郵件客戶端軟件 evolution 可以通過 D-Bus 從 NetworkManager 服務獲取網絡狀態的改變,以便做出相應的處理。
  • D-Bus 支持所謂”bus activation”功能。如果服務 A 需要使用服務 B 的 D-Bus 服務,而服務 B 並沒有運行,則 D-Bus 可以在服務 A 請求服務 B 的 D-Bus 時自動啟動服務 B。而服務 A 發出的請求會被 D-Bus 緩存,服務 A 會等待服務 B 啟動就緒。利用這個特性,依賴 D-Bus 的服務就可以實現並行啟動。

    並發啟動原理之三:解決文件系統依賴

  • 系統啟動過程中,文件系統相關的活動是最耗時的,比如掛載文件系統,對文件系統進行磁盤檢查(fsck),磁盤配額檢查等都是非常耗時的操作。在等待這些工作完成的同時,系統處於空閒狀態。那些想使用文件系統的服務似乎必須等待文件系統初始化完成才可以啟動。但是 systemd 發現這種依賴也是可以避免的。
  • Systemd 參考了 autofs 的設計思路,使得依賴文件系統的服務和文件系統本身初始化兩者可以並發工作。
    • autofs 可以監測到某個文件系統掛載點真正被訪問到的時候才觸發掛載操作,這是通過內核 automounter 模塊的支持而實現的。比如一個 open() 系統調用作用在”/misc/cd/file1″的時候,/misc/cd 尚未執行掛載操作,此時 open() 調用被掛起等待,Linux 內核通知 autofs,autofs 執行掛載。這時候,控制權返回給 open()系統調用,並正常打開文件。
  • Systemd 集成了 autofs 的實現,對於系統中的掛載點,比如/home,當系統啟動的時候,systemd 為其創建一個臨時的自動掛載點。在這個時刻/home 真正的掛載設備尚未啟動好,真正的掛載操作還沒有執行,文件系統檢測也還沒有完成。
  • 可是那些依賴該目錄的進程已經可以並發啟動,他們的 open() 操作被內建在 systemd 中的 autofs 捕獲,將該 open() 調用掛起(可中斷睡眠狀態)。然後等待真正的掛載操作完成,文件系統檢測也完成後,systemd 將該自動掛載點替換為真正的掛載點,並讓 open() 調用返回。由此,實現了那些依賴於文件系統的服務和文件系統本身同時並發啟動。
  • 當然對於”/“根目錄的依賴實際上一定還是要串行執行,因為 systemd 自己也存放在/之下,必須等待系統根目錄掛載檢查好。
  • 不過對於類似 /home 等掛載點,這種並發可以提高系統的啟動速度,尤其是當 /home 是遠程的 NFS 節點,或者是加密盤等,需要耗費較長的時間才可以準備就緒的情況下,因為並發啟動,這段時間內,系統並不是完全無事可做,而是可以利用這段空餘時間做更多 的啟動進程的事情,總的來說就縮短了系統啟動時間。

    Systemd 的使用

    系統軟件開發人員

  • 開發人員需要瞭解 systemd 的更多細節。比如您打算開發一個新的系統服務,就必須瞭解如何讓這個服務能夠被 systemd 管理。這需要您注意以下這些要點:
    • 後台服務進程代碼不需要執行兩次派生來實現後台精靈進程,只需要實現服務本身的主循環即可。
    • 不要調用 setsid(),交給 systemd 處理
    • 不再需要維護 pid 文件。
    • Systemd 提供了日誌功能,服務進程只需要輸出到 stderr 即可,無需使用 syslog。
    • 處理信號 SIGTERM,這個信號的唯一正確作用就是停止當前服務,不要做其他的事情。
    • SIGHUP 信號的作用是重啟服務。
    • 需要套接字的服務,不要自己創建套接字,讓 systemd 傳入套接字。
    • 使用 sd_notify() 函數通知 systemd 服務自己的狀態改變。一般地,當服務初始化結束,進入服務就緒狀態時,可以調用它。

      Unit 文件的編寫

  • 對於開發者來說,工作量最大的部分應該是編寫配置單元文件,定義所需要的單元。
    • 舉例來說,開發人員開發了一個新的服務程序,比如 httpd,就需要為其編寫一個配置單元文件以便該服務可以被 systemd 管理,類似 UpStart 的工作配置文件。在該文件中定義服務啟動的命令行語法,以及和其他服務的依賴關係等。
  • 此外我們之前已經瞭解到,systemd 的功能繁多,不僅用來管理服務,還可以管理掛載點,定義定時任務等。這些工作都是由編輯相應的配置單元文件完成的。
    • 下面是 SSH 服務的配置單元文件,服務配置單元文件以.service 為文件名後綴。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #cat /etc/system/system/sshd.service
      [Unit]
      Description=OpenSSH server daemon
      [Service]
      EnvironmentFile=/etc/sysconfig/sshd
      ExecStartPre=/usr/sbin/sshd-keygen
      ExecStart=/usrsbin/sshd –D $OPTIONS
      ExecReload=/bin/kill –HUP $MAINPID
      KillMode=process
      Restart=on-failure
      RestartSec=42s
      [Install]
      WantedBy=multi-user.target
      • 文件分為三個小節。第一個是[Unit]部分,這裡僅僅有一個 描述信息。第二部分是 Service 定義,其中,ExecStartPre 定義啟動服務之前應該運行的命令;ExecStart 定義啟動服務的具體命令行語法。第三部分是[Install],WangtedBy 表明這個服務是在多用戶模式下所需要的。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        #cat multi-user.target
        [Unit]
        Description=Multi-User System
        Documentation=man.systemd.special(7)
        Requires=basic.target
        Conflicts=rescue.service rescure.target
        After=basic.target rescue.service rescue.target
        AllowIsolate=yes
        [Install]
        Alias=default.target
      • 第一部分中的 Requires 定義表明 multi-user.target 啟動的時候 basic.target 也必須被啟動;另外 basic.target 停止的時候,multi-user.target 也必須停止。如果您接著查看 basic.target 文件,會發現它又指定了 sysinit.target 等其他的單元必須隨之啟動。同樣 sysinit.target 也會包含其他的單元。採用這樣的層層鏈接的結構,最終所有需要支持多用戶模式的組件服務都會被初始化啟動好。
      • 在[Install]小節中有 Alias 定義,即定義本單元的別名,這樣在運行 systemctl 的時候就可以使用這個別名來引用本單元。這裡的別名是 default.target,比 multi-user.target 要簡單一些。
      • 此外在/etc/systemd/system 目錄下還可以看到諸如*.wants 的目錄,放在該目錄下的配置單元文件等同於在[Unit]小節中的 wants 關鍵字,即本單元啟動時,還需要啟動這些單元。比如您可以簡單地把您自己寫的 foo.service 文件放入 multi-user.target.wants 目錄下,這樣每次都會被默認啟動了。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        #cat sys-kernel-debug.mount
        [Unit]
        Description=Debug File Syste
        DefaultDependencies=no
        ConditionPathExists=/sys/kernel/debug
        Before=sysinit.target
        [Mount]
        What=debugfs
        Where=/sys/kernel/debug
        Type=debugfs
      • 這個配置單元文件定義了一個掛載點。掛載配置單元文件有一個[Mount]配置小節,裡面配置了 What,Where 和 Type 三個數據項。這都是掛載命令所必須的,例子中的配置等同於下面這個掛載命令:mount –t debugfs /sys/kernel/debug debugfs

        系統管理員

  • systemd 的主要命令行工具是 systemctl。
  • 多數管理員應該都已經非常熟悉系統服務和 init 系統的管理,比如 service、chkconfig 以及 telinit 命令的使用。
  • systemd 也完成同樣的管理任務,只是命令工具 systemctl 的語法有所不同而已,因此用表格來對比 systemctl 和傳統的系統管理命令會非常清晰。
SYSVINIT 命令 SYSTEMD 命令 備註
service foo start systemctl start foo.service 用來啟動一個服務 (並不會重啟現有的)
service foo stop systemctl stop foo.service 用來停止一個服務 (並不會重啟現有的)。
service foo restart systemctl restart foo.service 用來停止並啟動一個服務。
service foo reload systemctl reload foo.service 當支持時,重新裝載配置文件而不中斷等待操作。
service foo condrestart systemctl condrestart foo.service 如果服務正在運行那麼重啟它。
service foo status systemctl status foo.service 匯報服務是否正在運行。
ls /etc/rc.d/init.d/ systemctl list-unit-files –type=service 用來列出可以啟動或停止的服務列表。
chkconfig foo on systemctl enable foo.service 在下次啟動時或滿足其他觸發條件時設置服務為啟用
chkconfig foo off systemctl disable foo.service 在下次啟動時或滿足其他觸發條件時設置服務為禁用
chkconfig foo systemctl is-enabled foo.service 用來檢查一個服務在當前環境下被配置為啟用還是禁用。
chkconfig –list systemctl list-unit-files –type=service 輸出在各個運行級別下服務的啟用和禁用情況
chkconfig foo –list ls /etc/systemd/system/*.wants/foo.service 用來列出該服務在哪些運行級別下啟用和禁用。
chkconfig foo –add systemctl daemon-reload 當您創建新服務文件或者變更設置時使用。
telinit 3 systemctl isolate multi-user.target (OR systemctl isolate runlevel3.target OR telinit 3) 改變至多用戶運行級別。
命令 操作
systemctl reboot 重啟機器
systemctl poweroff 關機
systemctl suspend 待機
systemctl hibernate 休眠
systemctl hybrid-sleep 混合休眠模式(同時休眠到硬盤並待機)
  • 關機不是每個登錄用戶在任何情況下都可以執行的,一般只有管理員才可以關機。正常情況下系統 不應該允許 SSH 遠程登錄的用戶執行關機命令。否則其他用戶正在工作,一個用戶把系統關了就不好了。為瞭解決這個問題,傳統的 Linux 系統使用 ConsoleKit 跟蹤用戶登錄情況,並決定是否賦予其關機的權限。現在 ConsoleKit 已經被 systemd 的 logind 所替代。

    • logind 不是 pid-1 的 init 進程。它的作用和 UpStart 的 session init 類似,但功能要豐富很多,它能夠管理幾乎所有用戶會話(session)相關的事情。logind 不僅是 ConsoleKit 的替代,它可以:

      • 維 護,跟蹤會話和用戶登錄情況。如上所述,為了決定關機命令是否可行,系統需要瞭解當前用戶登錄情況,如果用戶從 SSH 登錄,不允許其執行關機命令;如果普通用戶從本地登錄,且該用戶是系統中的唯一會話,則允許其執行關機命令;這些判斷都需要 logind 維護所有的用戶會話和登錄情況。
      • Logind 也負責統計用戶會話是否長時間沒有操作,可以執行休眠/關機等相應操作。
      • 為用戶會話的所有進程創建 CGroup。這不僅方便統計所有用戶會話的相關進程,也可以實現會話級別的系統資源控制。
      • 負責電源管理的組合鍵處理,比如用戶按下電源鍵,將系統切換至睡眠狀態。
      • 多席位(multi-seat) 管理。如今的電腦,即便一台筆記本電腦,也完全可以提供多人同時使用的計算能力。多席位就是一台電腦主機管理多個外設,比如兩個屏幕和兩個鼠標/鍵盤。席 位一使用屏幕 1 和鍵盤 1;席位二使用屏幕 2 和鍵盤 2,但他們都共享一台主機。用戶會話可以自由在多個席位之間切換。或者當插入新的鍵盤,屏幕等物理外設時,自動啟動 gdm 用戶登錄界面等。所有這些都是多席位管理的內容。ConsoleKit 始終沒有實現這個功能,systemd 的 logind 能夠支持多席位。
    • 以上描述的這些管理功能僅僅是 systemd 的部分功能,除此之外,systemd 還負責系統其他的管理配置,比如配置網絡,Locale 管理,管理系統內核模塊加載等,完整地描述它們已經超出了本人的能力。

systemd 小結

  • 作為系統初始化系統,systemd 的最大特點有兩個:
    • 令人驚奇的激進的並發啟動能力,極大地提高了系統啟動速度;
    • 用 CGroup 統計跟蹤子進程,乾淨可靠。

Reference