-
Type=forking
使用Type=forking時,要求ExecStart啟動的命令自身就是以daemon模式運行的。
而以daemon模式運行的進程都有一個特性:總是會有一個瞬間退出的中間父進程,例如,nginx命令默認以daemon模式運行,所以可直接將其配置為forking類型:
Type=simple是一種最常見的通過systemd服務系統(tǒng)運行用戶自定義命令的類型,也是省略Type指令時的默認類型。
例如,nginx命令默認以daemon模式運行,所以可直接將其配置為forking類型:

注意上面status報告的信息中,ExecStart啟動的nginx的進程PID=7912,且該進程的狀態(tài)是已退出,退出狀態(tài)碼為0,這個進程是daemon類進程創(chuàng)建過程中瞬間退出的中間父進程。在forking類型中,該進程稱為初始化進程。同時還有一行Main PID: 7913 (nginx),這是systemd真正監(jiān)控的nginx服務主進程,其PID=7913,是PID=7912進程的子進程。
Type=forking類型代表什么呢?要解釋清楚該type,需從進程創(chuàng)建開始說起。
因為systemd service啟動的服務進程都是systemd的子進程,所以,在服務進程啟動時,總是由pid=1的systemd進程fork()一個子進程(子systemd進程),再在此進程分支中通過systemd.exec配置該子進程的環(huán)境,最后使用exec()去調(diào)用ExecStart指定的服務啟動命令。Exec()調(diào)用程序時會替換當前進程,所以啟動后的服務進程將會替代子systemd進程,于是服務進程自身成為pid=1 systemd進程的子進程。
對于Type=forking來說,pid=1的systemd進程fork出來的子進程正是瞬間退出的中間父進程,且systemd會在中間父進程退出后就認為服務啟動成功,此時systemd可以立即去啟動后續(xù)需要啟動的服務。
如果Type=forking服務中的啟動命令是一個前臺 命令會如何呢?比如將sleep配置為forking模式,將nginx daemon off配置為forking模式等。
答案是systemd會一直等待中間ExecStart啟動的進程作為中間父進程退出,在等待過程中個,systemctl start會一直卡住,直到等待超時而失敗,在此階段中,systemctl status將會查看到服務處于activating狀態(tài)。

回到forking類型的服務。由于daemon類的進程會有一個瞬間退出的中間父進程(如上面的PID=7913的nginx進程),systemd是如何知道哪個進程是應該被監(jiān)控的服務主進程(Main PID)呢?
答案是靠猜。沒錯,systemd真的就是靠猜的。當設(shè)置Type=forking時,有一個GuessMainPID指令其默認值為Yes,它表示systemd會通過一些算法去猜測Main PID。當systemd的猜測無法確定哪個是主進程時,后果是嚴重的:systemd將不可靠。因為systemd無法正確探測服務是否真的失敗,當systemd誤認為服務失敗時,如果本服務器配置了自動重啟(配置了Restart指令),重啟服務器時可能會和當前正在運行但是systemd誤認為失敗的服務沖突(比如出現(xiàn)端口已被占用問題)。
多數(shù)情況下的猜測過程很簡單,systemd只需去找目前存活的屬于本服務的leader進程即可。但有些服務(少數(shù))情況可能比較復雜,在多進程之間做簡單的猜測并非總是可靠。
好在,Type=forking時的systemd提供了PIDFile指令(Type=forking通常都會結(jié)合PIDFile指令),systemd會從PIDFile指令所指定的PID文件中獲取服務的主進程PID。例如,編寫一個nginx的服務配置文件:

GuessMainPID=Takes a boolean value that specifies whether systemd should try to guess the main PID of a service if it cannot be determined reliably. This option is ignored unless Type=forking is set and PIDFile= is unset because for the other types or with an explicitly configured PID file, the main PID is always known. The guessing algorithm might come to incorrect conclusions if a daemon consists of more than one process. If the main PID cannot be determined, failure detection and automatic restarting of a service will not work reliably. Defaults to yes.
該參數(shù)只有在啟動類型為forking,且沒有指定PIDFile參數(shù)時才有效。
-
Type=forking時PIDFile指令的坑
關(guān)于PIDFile,有必要去了解一些注意事項,否則它們可能就會成為你的坑。
首先,PIDFile只適合在Type=forking模式下使用,其它時候沒必要使用,因為其它類型的Service主進程的PID都是確定的。systemd推薦PIDFile指定的PID文件在/run目錄下,所以,可能需要修改服務程序的配置文件,將其PID文件路徑修改為/run目錄之下,當然這并非必須。
但有一點必須注意,PIDFile指令的值要和服務程序的PID文件路徑保持一致。例如nginx的相關(guān)配置:

其次,systemd會在中間父進程退出后立即讀取這個PID文件,讀取成功后就認為該服務已經(jīng)啟動成功。但是,systemd讀取PIDFile的時候,服務主進程可能還未將PID寫入到PID文件中,這時systemd將出現(xiàn)問題。所以,對于服務程序的開發(fā)人員來說,應盡早將主進程寫入到PID文件中,比如可以在中間父進程fork完之后立即寫入PID文件,然后再退出,而不是在fork出來的服務主進程內(nèi)部由主進程負責寫入。
上面的nginx服務配置文件是某個nginx版本yum包提供的,但卻是有問題的,我曾經(jīng)踩過這個坑,網(wǎng)上甚至將其報告為一個Bug。
上面的nginx.service文件可以正常啟動服務,但無法systemctl reload,只要reload就報錯,而且報錯時提示kill命令語法錯誤。kill語法錯誤顯然是因為沒有獲取到$MAINPID變量的值,而這正是因為systemd在nginx寫入PID文件之前先去讀取了PID文件,因為沒有讀取到內(nèi)容,所以$MAINPID變量為空值。
解決辦法是使用ExecStartPost=/usr/bin/sleep 0.1,讓systemd在初始化進程(即中間父進程)退出之后耽擱0.1秒再繼續(xù)向下執(zhí)行,即推遲了systemd讀取PID的過程,保證能讓systemd從PID文件中讀取到值。
最后,systemd只會讀PIDFile文件而不會寫,也不會創(chuàng)建它。但是,在停止服務的時候,systemd會嘗試刪除PID文件。因為服務進程可能會異常終止,導致已終止的服務進程的PID文件仍然保留著,所以在使用PIDFile指令時,通常還會使用ExecStartPre指令來刪除可能已經(jīng)存在的PID文件。正如上面給出的nginx配置文件一樣。
-
Type=simple
Type=simple類型的服務只適合那些在shell下運行在前臺的命令。也就是說,當一個命令本身會以daemon模式運行時,將不能使用simple,而應該使用Type=forking。比如ls命令、sleep命令、非daemon模式運行的nginx進程以及那些以前臺調(diào)試模式運行的進程,在理論上都可以定義為simple類型的服務。
例如,編寫一個/usr/lib/systemd/system/test.service運行sleep進程:

使用daemon-reload重載并啟動該服務進程:

10秒內(nèi),sleep進程以daemon模式運行在后臺,就像一個服務進程一樣。10秒之后,sleep退出,于是systemd將該進程從監(jiān)控隊列中踢出。再次查看進程的狀態(tài)將是inactive:

再來分析上面的服務配置文件中的指令。
ExecStart指令指定啟動本服務時執(zhí)行的命令,即啟動一個本該前臺運行的sleep進程作為服務進程在后臺運行。
需注意,systemd service的命令行中必須使用絕對路徑,且只能編寫單條命令(Type=oneshot時除外),如果要命令續(xù)行,可在尾部使用反斜線符號\等。
此外,命令行中支持部分類似Shell的特殊符號,但不支持重定向> >> << <、管道|、后臺符號&,具體可參考man systemd.service中command line段落的解釋說明。
對于Type=simple來說,systemd系統(tǒng)在fork出子systemd進程后就認為服務已經(jīng)啟動完成了,所以systemd可以緊跟著啟動排在該服務之后啟動的服務。它的偽代碼模型大概是這樣的:

例如,先后連續(xù)啟動兩個Type=simple的服務,進程流程圖大概如下:
換句話說,當Type=simple時,systemd只在乎fork階段是否成功,只要fork子進程成功,這個子進程就受systemd監(jiān)管,systemd就認為該Unit已經(jīng)啟動。
因為子進程已成功被systemd監(jiān)控,無論子進程是否啟動成功,在子進程退出時,systemd都會將其從監(jiān)控隊列中踢掉,同時殺掉所有附屬進程(默認行為是如此,殺進程的方式由systemd.kill中的KillMode指令控制)。所以,查看服務的狀態(tài)將是inactive(dead)。
例如,下面的配置種,睡眠1秒后,該服務的狀態(tài)將變?yōu)閕nactive(dead)。

這沒什么疑問。但考慮一下,如果simple類型下ExecStart啟動的命令本身就是以daemon模式運行的呢?其結(jié)果是systemd默認會立刻殺掉所有屬于服務的進程。
原因也很簡單,daemon類進程總是會有一個瞬間退出的中間父進程,而在simple類型下,systemd所fork出來的子進程正是這個中間父進程,所以systemd會立即發(fā)現(xiàn)這個中間父進程的退出,于是殺掉其它所有服務進程。
例如,以運行bash -c '(sleep 3000 &)'的simple類型的服務,被systemd監(jiān)控的bash進程會在啟動sleep后立即退出,于是systemd會立即殺掉屬于該服務的sleep進程。

再例如,nginx命令默認是以daemon模式運行的,simple類型下直接使用nginx命令啟動服務,systemd會立刻殺掉所有nginx,即nginx無法啟動成功。
-
Systemd Service:其它Type類型
simple:在fork出子systemd進程后,systemd就認為該服務啟動成功了
exec:在fork出子systemd進程且子systemd進程exec()調(diào)用ExecStart命令成功后,systemd認為該服務啟動成功
oneshot:在ExecStart命令執(zhí)行完成退出后,systemd才認為該服務啟動成功
因為服務進程退出后systemd才繼續(xù)工作,所以在未配置RemainAfterExit 指令時,oneshot類型的服務永遠無法出現(xiàn)active狀態(tài),它直接從啟動狀態(tài)到activating到deactivating再到dead狀態(tài)
當結(jié)合RemainAfterExit指令時,在服務進程退出后,systemd會繼續(xù)監(jiān)控該Unit,所以服務的狀態(tài)為active(exited),通過這個狀態(tài)可以讓用戶知道,該服務曾經(jīng)已經(jīng)運行成功,而不是從未運行過文章來源:http://www.zghlxwxcb.cn/news/detail-769173.html
通常來說,對于那些執(zhí)行單次但無需長久運行的進程來說,可以采用type=oneshot,比如啟動iptables,掛載文件系統(tǒng)的操作、關(guān)機或重啟的服務等文章來源地址http://www.zghlxwxcb.cn/news/detail-769173.html
到了這里,關(guān)于Linux Systemd type=simple和type=forking的區(qū)別的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!