什么是systemd
systemd即為system daemon,是linux下的一種init軟件,由Lennart Poettering帶頭開發,並在LGPL 2.1及其后續版本許可證下開源發布,開發目標是提供更優秀的框架以表示系統服務間的依賴關系,並依此實現系統初始化時服務的並行啟動,同時達到降低Shell的系統開銷的效果,最終代替現在常用的System V與BSD風格init程序。
與多數發行版使用的System V風格init相比,systemd采用了以下新技術:
(1) 采用Socket激活式與總線激活式服務,以提高相互依賴的各服務的並行運行性能;
(2) 用Cgroups代替PID來追蹤進程,以此即使是兩次fork之后生成的守護進程也不會脫離systemd的控制。
systemd已納入眾多Linux發行版的軟件源中,以下簡表:
Fedora 15及后續版本
Mageia 2
Mandriva 2011
openSUSE 12.1及后續版本
Arch Linux在2012年10月13日將systemd-sysvcompat納入base軟件組,自此Arch Linux默認安裝完即以systemd為init程序[13],同時也提供了與Arch自帶啟動腳本兼容用的systemd啟動腳本包以方便用戶,使用戶能“開箱即用”
作用
作為init軟件,systemd程序的任務有如下工作
(1) 初始化文件系統,設置環境變量
(2) 掛載硬盤,/proc, /tmp, swap等等
(3) 根據設置的運行級別, 啟動相應的守護進程
(4) 並在系統運行期間,監聽整個文件系統
systemd 開啟和監督整個系統是基於 unit 的概念。unit 是由一個與配置文件對應的名字和類型組成的. 一個unit配置文件,封裝了后台服務,socket,設備,掛載,自動掛載,交換文件或分區,啟動目標,文件系統路徑,或者定時器控制,這些其中的一種。配置文件語法源於XDG Desktop Entry Specification的.desktop文件,微軟的.ini文件語法格式。
所有的unit文件都應該配置[Unit]或者[Install]段.由於通用的信息在[Unit]和[Install]中描述,每一個unit應該有一個指定類型段,例如[Service]來對應后台服務類型unit.
unit 類型如下:
service :守護進程的啟動、停止、重啟和重載是此類 unit 中最為明顯的幾個類型。
socket :此類 unit 封裝系統和互聯網中的一個socket。當下,systemd支持流式, 數據報和連續包的AF\_INET,AF\_INET6,AF\_UNIX socket 。也支持傳統的 FIFOs 傳輸模式。每一個 socket unit 都有一個相應的服務 unit 。相應的服務在第一個“連接”進入 socket 或 FIFO 時就會啟動(例如:nscd.socket 在有新連接后便啟動 nscd.service)。
device :此類 unit 封裝一個存在於 Linux 設備樹中的設備。每一個使用 udev 規則標記的設備都將會在 systemd 中作為一個設備 unit 出現。udev 的屬性設置可以作為配置設備 unit 依賴關系的配置源。
mount :此類 unit 封裝系統結構層次中的一個掛載點。
automount :此類 unit 封裝系統結構層次中的一個自掛載點。每一個自掛載 unit 對應一個已掛載的掛載 unit (需要在自掛載目錄可以存取的情況下盡早掛載)。
target :此類 unit 為其他 unit 進行邏輯分組。它們本身實際上並不做什么,只是引用其他 unit 而已。這樣便可以對 unit 做一個統一的控制。(例如:multi-user.target 相當於在傳統使用 SysV 的系統中運行級別5);bluetooth.target 只有在藍牙適配器可用的情況下才調用與藍牙相關的服務,如:bluetooth 守護進程、obex 守護進程等)
snapshot :與 target unit 相似,快照本身不做什么,唯一的目的就是引用其他 unit 。
配置文件
所有配置文件存放的目錄可以在以下任一目錄之中
/etc/systemd/system /etc/systemd/system/
/usr/lib/systemd/system/ /lib/systemd/system/
加載的第一個文件為default.target, systemd的啟動邏輯順序默認如下
local-fs-pre.target | v (various mounts and (various swap (various cryptsetup fsck services...) devices...) devices...) (various low-level | | | services: udevd, v v v tmpfiles, random local-fs.target swap.target cryptsetup.target seed, sysctl, ...) | | | | \__________________|_______________ | _________________/ \|/ v sysinit.target | _______________/|\___________________ / | \ | | | v | v (various | rescue.service sockets...) | | | | v v | rescue.target sockets.target | | | \_______________ | \| v basic.target | ________________________________/| emergency.service / | | | | | | v v v v emergency.target display- (various system (various system manager.service services services) | required for | | graphical UIs) v | | multi-user.target | | | \_______________ | _______________/ \|/ v graphical.target (如果,我們的啟動級別為5, 那么我們可以將 default.target 鏈接到graphical.target上)
例如我們需要添加一個服務, 應用程序為example, 那么我們在目錄/usr/lib/systemd/system/下創建文件example.service
[Unit] Description=Example Service [Service] ExecStart=/usr/bin/example [Install] WantedBy=multi-user.target
如果我們監視該程序,當程序退出時,能夠自動重新啟動。那么我們可以設置如下
[Unit] Description=Example Service [Service] ExecStart=/usr/bin/example Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Restart=可選項為
no:程序退出不重啟
on-success:正常退出時,終止並返回退出碼為0時重啟
on-failure:終止並返回退出碼為非0時重啟
on-abort:非正常退出時重啟,如段錯誤,看門狗超時等
always:任何情況下都重啟。
如果我們要求該服務在某些服務啟動之后,才能啟動。比如要求在dbus.service啟動之后,
再啟動我們的example.service,我們需要設置如下.
[Unit] Description=Example Service After=dbus.service [Service] ExecStart=/usr/bin/example Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
執行一個腳本
如我們需要在開機初始時,執行一個shell腳本,並且我們在腳本內部會啟動某些后台服務程序。那么我們在寫service文件的時候,則需要加入Type=forking段,否則shell執行結束退出后,
該腳本啟動的后台服務程序,也會結束.
[Unit] Description=panda launcher Wants=syslog.target dbus.service [Service] Type=forking ExecStart=/usr/bin/panda-launcher [Install] Alias=display-manager.service WantedBy=graphical.target
Service的Type幾種類型介紹
simple模式 :ExecStart=設置的程序為服務的主進程,這種模式下,如果該進程為其他進程提供通信管道,應該在服務進程啟動之前安裝好通信管道(例如:systemd需要的socket),systemd會立即啟動接下來的unit.
forking模式:將會調用fork()函數執行ExecStart=配置的主程序,當啟動和通信管道建立完成后,父進程則退出。
idle模式 :和simple模式相似,但是實際上執行啟動程序延遲到所有的job處理完成之后。
注:默認為simple模式
特殊的unit
一些unit被systemd特別的處理。他們有特別的內部語法,並且不能修改名稱。basic.target, bluetooth.target, ctrl-alt-del.target, cryptsetup.target, dbus.service, dbus.socket, default.target, 等等.
調試
根據類型列舉出當前的狀態
systemctl list-units -t service --all
列舉出所有的service和他們的當前狀態
systemctl status sshd.service
檢查當前運行中服務的狀態
systemctl show -p "Wants" multi-user.target
列出一個target組合着哪些service.
systemd的mount會去根據/etc/fstab內容,進行掛載
函數分析
+---------+ | manager | +---------+ | +-------------------------------+ | | | | +-------+ +------+ +------+ +------+ |service| |target| |socket| | ... | +-------+ +------+ +------+ +------+ | | | | +-------------------------------+ | +---------+ +-----+ | unit |<---->| job | +---------+ +-----+
main中的manager
+-------------+ +->| manager_new | set path | +-------------+ | | +-----------------+ +->| manager_startup | read file name | +-----------------+ | +------+ | +-------------------+ | main |-+->| manager_load_unit | anaylse file +------+ | +-------------------+ | | +-----------------+ +->| manager_add_job | add each target to job | +-----------------+ | | +--------------+ +->| manager_loop | +--------------+
設置路徑部分代碼
int main() { if ((r = manager_new(arg_running_as, &m)) < 0) { goto finish; } } int manager_new(ManagerRunningAs running_as, Manager **_m) { if ((r = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) goto fail } int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as, bool personal) { if (running_as == MANAGER_USER) { } else if (!(p->unit_path = strv_new( /* If you modify this you also want to modify * systemdsystemunitpath= in systemd.pc.in! */ "/run/systemd/system", SYSTEM_CONFIG_UNIT_PATH, "/etc/systemd/system", "/usr/local/lib/systemd/system", "/usr/lib/systemd/system", SYSTEM_DATA_UNIT_PATH, "/lib/systemd/system", NULL))) }
加載文件流程圖
+------+ +-------------+ +-------------------+ +-----------------+ | main |------| manager_new |---| lookup_paths_init |---| m->lookup_paths | +------+ | +-------------+ +-------------------+ +-----------------+ | +-----------------+ set path +->| manager_startup | +-----------------+ | +-------------------------------+ | manager_build_unit_path_cache | +-------------------------------+ | +--------------------------------------------+ | STRV_FOREACH(i, m->lookup_paths.unit_path) | | set_put(m->unit_path_cache, p) |-------unit_path_cache +--------------------------------------------+ +-------------+ | iterate_dir | +-------------+ | +------------------------------------------------+ | set_get(u->manager->unit_path_cache, filename) | +------------------------------------------------+ | +----------------+ | load_from_path | +----------------+ | |.wants .requires +--------------------+ +------------------+ | unit_load_fragment | | unit_load_dropin | +--------------------+ +------------------+ | | +------------------------------------------+ | +------------------------------------+---------------+ | | | +-------------------------------+ +--------------+ | unit_load_fragment_and_dropin | | service_load | ...... +-------------------------------+ +--------------+
main() { /* Initialize default unit */ if (set_default_unit(SPECIAL_DEFAULT_TARGET) < 0) goto finish; // arg_default_unit is "default.target" manager_load_unit(m, arg_default_unit, NULL, &error, &target)) < 0) if ((r = manager_add_job(m, JOB_START, target, JOB_REPLACE, false, &error, NULL)) < 0) { goto finish; } } int manager_load_unit() { /* This will load the service information files, but not actually * start any services or anything. */ if ((r = manager_load_unit_prepare(m, name, path, e, _ret)) != 0) return r; manager_dispatch_load_queue(m); } unsigned manager_dispatch_load_queue(Manager *m) { while ((meta = m->load_queue)) { unit_load((Unit*) meta); } } int unit_load(Unit *u) { if (u->meta.in_load_queue) { //remove unit frome load_queue LIST_REMOVE(Meta, load_queue, u->meta.manager->load_queue, &u->meta); u->meta.in_load_queue = false; } if (UNIT_VTABLE(u)->load) if ((r = UNIT_VTABLE(u)->load(u)) < 0) goto fail; if (u->meta.load_state == UNIT_LOADED && u->meta.default_dependencies) if ((r = unit_add_default_dependencies(u)) < 0) goto fail; } //take service load for example static int service_load(Unit *u) { /* Load a .service file */ if ((r = unit_load_fragment(u)) < 0) return r; /* We were able to load something, then let's add in the * dropin directories. */ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) return r; } int unit_load_fragment(Unit *u) { if ((r = load_from_path(u, u->meta.id)) < 0) return r; } static int load_from_path(Unit *u, const char *path) { if (path_is_absolute(path)) { } else { STRV_FOREACH(p, u->meta.manager->lookup_paths.unit_path) { if (!(filename = path_make_absolute(path, *p))) { } } } }
執行
+--------------+ +----------------------------+ | manager_loop |---->| manager_dispatch_run_queue | +--------------+ +----------------------------+ | +--------------------------+ | if(j = m->run_queue) | | job_run_and_invalidate | +--------------------------+ | +----------------------------------------------+ |LIST_REMOVE(Job,...,j->manager->run_queue, j);| +---------------------+ | unit_start |->|UNIT_VTABLE(u)->start| +----------------------------------------------+ +---------------------+ | +-----------------------+ +---------------------+ +-------------+ | service_vtable = { | | service_enter_start |<--|service_start|<--|.suffix = ".service" | +---------------------+ +-------------+ |.start = service_start,| | |}; | +---------------+ +-----------------------+ | service_spawn | +---------------+ | +------------+ | exec_spawn | +------------+
並發
例如 SysV的系統啟動,一次只能啟動一個進程,例如將會有如下的啟動順序, Syslog -> D-Bus -> Avahi -> Blutooth. 一些版本試圖改進嚴格的按照序列化啟動,如Avahi和Bluetooth彼此獨立,他們能夠並發的啟動。但是這個並發改進的效果並不明顯.
Socket激活,使得同時並發四個服務沒有任何順序排列成為可能,既然特定的監聽socket從后台中拿出來,我們就可以同時啟動他們,並且他們能夠立即互相連接. 如,第一步,創建/dev/log和/run/dbus/system\_bus\_socket這兩個socket,然后同時啟動四個服務.當D-Bus想記錄log到syslog時,就將消息寫入到/dev/log中,只要socket buffer沒有運行滿,就不能夠立即執行,並繼續進行他的初始化.當syslog服務啟動,他將會處理socket buffer中的隊列消息.如果socket buffer滿了,那么client的logging將會被阻塞,直到socket再次可寫.
Socket激活還需要service能夠從systemd接收預先初始化的socket. 來代替內部初始化.
例如: dbus-daemon, 需要進行修改,源代碼中dbus/sd-daemon.c的內容和 systemd/src/sd-daemon.c代碼相同。
參考
http://0pointer.de/blog/projects/systemd-docs.html
http://en.wikipedia.org/wiki/Systemd
http://fedoraproject.org/wiki/Systemd/zh-cn