本文目錄: 1.1 SysV腳本的特性1.2 SysV腳本要具備的能力1.3 start函數分析1.4 stop函數分析1.5 reload函數分析1.6 status、restart、force-reload等1.7 結束語 SysV服務管理腳本和/etc/rc.d/init.d/function ...
1.1 SysV腳本的特性
1.2 SysV腳本要具備的能力
1.3 start函數分析
1.4 stop函數分析
1.5 reload函數分析
1.6 status、restart、force-reload等
1.7 結束語
SysV服務管理腳本和/etc/rc.d/init.d/functions文件中的幾個重要函數(包括daemon
,killproc
,status
以及幾個和pid有關的函數)"關係匪淺"。本人已對該文件做了極詳細的分析和說明,參考functions文件詳細分析和說明。
1.1 SysV腳本的特性
SysV風格的服務啟動腳本有以下幾個特性:
- 一般都放在/etc/rc.d/init.d目錄下。
- 這類腳本要求能接受start、stop、restart、status等參數來管理服務進程。
- 基本上都會載入/etc/rc.d/init.d/functions文件,因為該文件中定義了幾個對進程管理非常有用的函數。
- 基本上都會載入/etc/sysconfig目錄下的同名文件。此目錄下的服務同名文件一般都是為服務管理腳本提供選項參數的。例如/etc/sysconfig/httpd。
- 在腳本的頂端,需要加上
# chkconfig
和# description
兩行。chkconfig行定義的是該腳本被chkconfig工具管理時的主要依據,包括開機和關機時的啟動、關閉順序,以及運行在哪些運行級別。description是該腳本的描述性語句。雖然這兩行以"#"開頭,但必不可少。
例如,/etc/init.d/httpd腳本的前面幾行內容如下:
#!/bin/bash
#
# httpd Startup script for the Apache HTTP Server
#
# chkconfig: - 85 15
# description: The Apache HTTP Server is an efficient and extensible \
# server implementing the current HTTP standards.
# processname: httpd
# config: /etc/httpd/conf/httpd.conf
# config: /etc/sysconfig/httpd
# pidfile: /var/run/httpd/httpd.pid
#
# Source function library.
. /etc/rc.d/init.d/functions
if [ -f /etc/sysconfig/httpd ]; then # 判斷後再載入
. /etc/sysconfig/httpd
fi
1.2 SysV腳本要具備的能力
要使用腳本管理服務進程,該腳本還要求具備以下能力,且處理邏輯越完善,腳本就越完美。
- 啟動進程時:
- 要求能夠檢測進程是否已在運行。這包括檢測pid文件是否存在、/proc目錄下是否有進程pid值對應的目錄。
- 應該為程式創建鎖文件,路徑一般在/var/lock/subsys目錄下。
- 如果使用daemon函數啟動進程,允許"--user"指定程式的運行身份。
- 有些進程啟動時需要依賴於其他進程,如NFS啟動時依賴於rpcbind服務、mountd服務等,所以在NFS腳本中必須能夠檢測並啟動這些依賴服務。
- 關閉進程時:
- 要求能夠檢測進程是否已在運行。同樣是檢測pid文件是否存在,/proc目錄下是否有pid對應的目錄。要註意,只有/proc目錄下沒有了對應目錄,才表示進程已死,但pid文件仍可能存在,例如
kill -9
就會出現這種問題。 - 可以使用functions文件中的killproc函數殺進程,也可以直接使用kill或killall。
- 為了讓腳本更完善,殺進程時應該多次檢測進程是否真的已經殺死。
- 殺死進程的最後,必須要刪除pid文件和鎖文件。
- 對於有依賴性的服務,考慮是否也應該殺死它們。
- 要求能夠檢測進程是否已在運行。同樣是檢測pid文件是否存在,/proc目錄下是否有pid對應的目錄。要註意,只有/proc目錄下沒有了對應目錄,才表示進程已死,但pid文件仍可能存在,例如
- 服務重讀配置文件時(reload):
- 對於非終端進程,發送HUP信號的作用是重讀配置文件,而不會中斷進程。
- 為了標準,應該找出"master"進程的pid,並向其發送HUP信號。一般來說,服務的子進程或線程不會也沒必要讀取配置文件。為了方便或投籃,可以直接向所有進程發送HUP信號。
- 最好在發送HUP信號前,也檢查進程是否已在運行。當然,對於reload來說,這無所謂。
- 如果待管理程式支持配置文件的語法檢查,在發送HUP信號前,應該檢查語法是否錯誤。
- 實在無法實現重讀配置文件的功能,應該讓其和restart的功能一致,一般這也是"force-reload"的功能。
- 重啟服務時:
- 一般來說,就是先stop,再start。
- 查看status時:
- 除非有額外的狀態顯示需求,否則/etc/init.d/functions中的status函數已經足夠完美了。
以上並沒有說明,管理多實例服務時的情況。這需要考慮額外的因素,例如程式自身是否支持多實例,支持的話是否應該寫多個服務腳本分別管理各程式,配置文件是否要共用,pid文件是否能共用,搜索pid時如何避免搜索出非自身實例的pid,還要註意分配鎖文件。這樣的腳本寫起來可能並不難,但這些因素必須要考慮。本文暫不介紹多實例的SysV腳本,因為和程式自身關聯性比較強。
有了以上內容,並理解了functions文件中的函數,再看/etc/init.d/下的服務啟動腳本,絕大多數都感覺很簡單。因為它們的思路和框架都是一致的。
因為網上以及/etc/init.d/下服務啟動腳本示例太多了,所以本文不單獨寫這類腳本,而是從幾個腳本中抽出比較經典的部分,分別介紹start,stop,reload和status的寫法。
1.3 start函數分析
以httpd的服務管理腳本/etc/init.d/httpd為例。
start() {
echo -n $"Starting $prog: "
LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
函數首先輸出"Starting $prog"信息,再使用daemon啟動"$httpd"程式。
在daemon語句中,"--pidfile"是daemon的參數,該參數為daemon檢測pid文件是否存在,"$httpd"進程是否已在運行。註意,這個"--pidfile"是寫在"$httpd"前面的,表示這是daemon的參數,而非"$httpd"的啟動參數。
檢測完成後,啟動程式。程式的啟動命令從"$httpd"參數開始,"$OPTIONS"是"$httpd"的啟動選項。一般出現"$OPTIONS"這個字眼,很可能載入了/etc/sysconfig目錄下的同名文件,目的是提供程式啟動參數。
如果啟動成功,則會daemon函數會調用functions中的success函數顯示"[ OK ]",否則會顯示"[ FAILED ]"。
最後,如果啟動成功,則會創建該進程的鎖文件"$lockfile"。鎖文件一般都在/var/lock/subsys目錄下。
很多時候,管理的進程也有"--pidfile"類似的選項。例如下麵的啟動語句:
daemon --pidfile $pidfile $processname --pidfile=$pidfile
兩個"--pidfile"選項,但他們的作用是不一樣的。第一個"--pidfile"是daemon函數的參數,以便daemon能夠檢測該文件中的pid進程是否已在運行。第二個"--pidfile"是"$processname"的啟動參數,啟動時會創建此文件作為pid文件。
再看一個不使用daemon函數管理進程啟動動作的示例。以下是/etc/init.d/sshd中的start函數內容。
start()
{
[ -x $SSHD ] || exit 5
[ -f /etc/ssh/sshd_config ] || exit 6
# Create keys if necessary
if [ "x${AUTOCREATE_SERVER_KEYS}" != xNO ]; then
do_rsa_keygen
if [ "x${AUTOCREATE_SERVER_KEYS}" != xRSAONLY ]; then
do_rsa1_keygen
do_dsa_keygen
fi
fi
echo -n $"Starting $prog: "
$SSHD $OPTIONS && success || failure
RETVAL=$?
[ $RETVAL -eq 0 ] && touch $lockfile
echo
return $RETVAL
}
前面多了一大段,這和服務啟動腳本的框架無關,是程式自身要求的,但作用很簡單。無非就是判斷下程式是否可執行,配置文件是否存在,是否要創建服務端主機驗證階段的密鑰對,也就是/etc/ssh/ssh_host_{rsa,dsa}_key等幾個文件。
再下麵才是服務啟動腳本中的通用邏輯部分。輸出一段信息,然後啟動程式,創建鎖文件。但這裡沒有使用daemon函數管理,所以這裡配合了success和failure函數以便人性化顯示"[ OK ]"或"[ FAILED ]"。
1.4 stop函數分析
仍然以/etc/init.d/httpd中的stop函數為例。
# When stopping httpd, a delay (of default 10 second) is required
# before SIGKILLing the httpd parent; this gives enough time for the
# httpd parent to SIGKILL any errant children.
stop() {
status -p ${pidfile} $httpd > /dev/null
if [[ $? = 0 ]]; then
echo -n $"Stopping $prog: "
killproc -p ${pidfile} -d ${STOP_TIMEOUT} $httpd
else
echo -n $"Stopping $prog: "
success
fi
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}
前面加了一段註釋,大致意思是說這裡殺死進程的行為和httpd自帶的apachectl工具停止服務的命令"apachectl -k stop"的行為是不同的。之所以我要把這一段也貼上來,也就是為了說明這一點。有些服務程式自帶進程管理工具,亦或是使用functions中的函數,完全由我們自己決定。
再看stop函數的邏輯。首先使用"status"函數檢查進程的狀態,如果進程已在運行,則使用killproc函數殺掉它,否則表示進程未運行或進程已死,但pid文件還存在。所以,在最後刪掉pidfile和lockfile。
需要註意的是,killproc殺進程時,能保證pidfile同時被刪除。但它不負責lockfile,而且執行stop之前曾手動執行了"kill -9"殺進程,那麼進程雖然已死,但pid文件卻存在。因此也仍需手動rm刪除pidfile。
killproc的調用方法為:
killproc [-p $pidfile] -[d $delay] $processname [-signal]
它的邏輯和執行過程是這樣的:
- 根據pidfile找出要殺的pid,如果沒有指定pidfile,則預設從/var/run/$base.pid讀取;
- 如果指定了要發送的信號,則killproc通過kill命令發送給定信號。0.5秒後檢查/proc目錄下是否還有對應目錄存在,有則說明進程殺死失敗,返回"[ FAILED ]"信息,否則表示成功,於是刪除pid文件。
- 如果沒有指定要發送的信號,則killproc先發送TERM信號(即
kill -15
),然後在給定的延遲時間delay內,每隔一秒檢查一次/proc下是否有對應目錄,如果發現沒有,則表示進程殺死成功,於是刪除pid文件(其實這種情況不用刪,因為TERM信號會自動做收尾動作)。但如果delay都超時了,還發現進程存在,則發送KILL信號強制殺死進程,最後刪除pid文件。
現在再理解killproc -p ${pidfile} -d ${STOP_TIMEOUT} $httpd
就很簡單了。
再看/etc/init.d/sshd腳本中的stop。
stop()
{
echo -n $"Stopping $prog: "
killproc -p $PID_FILE $SSHD
RETVAL=$?
# if we are in halt or reboot runlevel kill all running sessions
# so the TCP connections are closed cleanly
if [ "x$runlevel" = x0 -o "x$runlevel" = x6 ] ; then
trap '' TERM
killall $prog 2>/dev/null
trap TERM
fi
[ $RETVAL -eq 0 ] && rm -f $lockfile
echo
}
更直接,直接就killproc。但是後面還設置了runlevel的判斷情況,這就屬於程式自身屬性了,和服務管理腳本的邏輯框架無關。
最後再看mysqld中的stop函數。
stop(){
if [ ! -f "$mypidfile" ]; then
# not running; per LSB standards this is "ok"
action $"Stopping $prog: " /bin/true # pid文件都不存在,直接顯示成功
return 0
fi
MYSQLPID=`cat "$mypidfile" 2>/dev/null` # 讀取pidfile中的pid號
if [ -n "$MYSQLPID" ]; then # 如果pid不為空,則
/bin/kill "$MYSQLPID" >/dev/null 2>&1 # 先發送預設的TERM信號殺一次
ret=$?
if [ $ret -eq 0 ]; then # 如果殺成功了,則執行下麵一段。
# 否則直接失敗,但這不可能。為了邏輯完整,後面仍寫了else
TIMEOUT="$STOPTIMEOUT"
while [ $TIMEOUT -gt 0 ]; do # 在延遲時間內,每隔1秒殺一次
/bin/kill -0 "$MYSQLPID" >/dev/null 2>&1 || break
sleep 1
let TIMEOUT=${TIMEOUT}-1
done
if [ $TIMEOUT -eq 0 ]; then # 如果達到延遲時間邊界,則返回殺死進程超時信息
echo "Timeout error occurred trying to stop MySQL Daemon."
ret=1
action $"Stopping $prog: " /bin/false
else # 否則進程殺死成功,刪除pidfile和lockfile
rm -f $lockfile
rm -f "$socketfile"
action $"Stopping $prog: " /bin/true
fi
else
action $"Stopping $prog: " /bin/false
fi
else # 如果pid為空,則表示未成功讀取pidfile。
# failed to read pidfile, probably insufficient permissions
action $"Stopping $prog: " /bin/false
ret=4
fi
return $ret
}
雖然有點長,但有了前面SysV腳本要具備的能力的概念,stop函數的邏輯都一樣好簡單。
1.5 reload函數分析
關於reload函數,主要有兩點:(1).語法檢查;(2).發送HUP信號給"master"進程。其中語法檢查要程式自身能支持,例如httpd -t
,nginx -t
。
以下是/etc/init.d/{httpd,nginx}兩個腳本中的reload函數。
## reload() in /etc/rc.d/init.d/httpd
reload() {
echo -n $"Reloading $prog: "
if ! LANG=$HTTPD_LANG $httpd $OPTIONS -t >&/dev/null; then # 語法檢查
RETVAL=6
echo $"not reloading due to configuration syntax error"
failure $"not reloading $httpd due to configuration syntax error"
else
# Force LSB behaviour from killproc # 語法檢查通過,發送HUP信號
LSB=1 killproc -p ${pidfile} $httpd -HUP
RETVAL=$?
if [ $RETVAL -eq 7 ]; then # 註意reload失敗時退出狀態碼為7
failure $"httpd shutdown"
fi
fi
echo
}
## reload() in /etc/rc.d/init.d/nginx
reload() {
configtest_q || return 6 # 語法檢查
echo -n $"Reloading $prog: "
killproc -p $pidfile $prog -HUP # 發送HUP信號
echo
}
configtest_q() {
$nginx -t -q -c $NGINX_CONF_FILE
}
case "$1" in
reload)
rh_status_q || exit 7 # reload失敗時,退出狀態碼7
$1
;;
唯一需要註意的是,reload失敗時,退出狀態碼為7。這大概已經約定俗成了吧。
再看/etc/init.d/sshd中的reload。
reload()
{
echo -n $"Reloading $prog: "
killproc -p $PID_FILE $SSHD -HUP
RETVAL=$?
echo
}
case "$1" in
reload)
rh_status_q || exit 7
reload
;;
有意思的是mysqld的reload。它直接退出不做任何動作。
case "$1" in
reload)
exit 3
;;
如果不使用killproc函數,而是使用kill命令,那麼應該找出"master" pid。可以使用functions中的pidofproc函數。例如:
pid=$(pidofprco -p pidfile $processname)
action "Reloading $prog: " kill -HUP $pid
1.6 status、restart、force-reload等
- status:就是為了獲取進程狀態的,一般直接調用functions中的status函數
status -p "$pidfile" $prog
。 - restart:一般直接stop再start即可。
- force-reload:其實就是restart。
- condrestart:稱為條件式重啟。所謂的條件一般是判斷鎖文件是否存在,存在則重啟,否則忽略該動作。"try-restart"也是一樣的行為。
1.7 結束語
其實SysV服務啟動腳本大多都很簡單,至少它們的邏輯幾乎都一樣。在瞭解了functions中的幾個函數後,再把腳本的各參數(如start、stop)應該要具備的能力搞搞清楚,這類腳本完全是小菜一兩碟。
回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html