守護(hù)進(jìn)程是生存期長(zhǎng)的一種進(jìn)程。它們獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件深胳。它們常常在系統(tǒng)引導(dǎo)裝入時(shí)啟動(dòng),在系統(tǒng)關(guān)閉時(shí)終止复罐。
守護(hù)進(jìn)程的特性
1.在后臺(tái)運(yùn)行
2.與其運(yùn)行的環(huán)境隔離開來匣掸。這些環(huán)境包括未關(guān)閉的文件描述符趟紊、控制終端、會(huì)話和進(jìn)程組碰酝、工作目錄以及文件創(chuàng)建掩碼等霎匈。這些環(huán)境通常是守護(hù)進(jìn)程從它執(zhí)行它的父進(jìn)程(特別是shell)中繼承下來的
3.啟動(dòng)方式特殊,它可以在系統(tǒng)啟動(dòng)時(shí)從啟動(dòng)腳本/etc/rc.d中啟動(dòng)送爸,可以由inetd守護(hù)進(jìn)程啟動(dòng)铛嘱,可以由crond啟動(dòng)暖释,還可以由用戶終端(通常是shell)執(zhí)行
總之,除開這些特殊性以外墨吓,守護(hù)進(jìn)程與普通進(jìn)程基本上沒有什么區(qū)別饭入。因此,編寫守護(hù)進(jìn)程實(shí)際上把一個(gè)普通進(jìn)程按照上述的守護(hù)進(jìn)程的特性改造成為守護(hù)進(jìn)程肛真。
守護(hù)進(jìn)程編程規(guī)則
目的:使子進(jìn)程不會(huì)擁有控制終端谐丢,即不要繼承父進(jìn)程的進(jìn)程組id和會(huì)話組id,也就是使子進(jìn)程成為進(jìn)程組長(zhǎng)和會(huì)話組長(zhǎng)蚓让。
1.創(chuàng)建子進(jìn)程乾忱。fork產(chǎn)生子進(jìn)程,由于有父進(jìn)程历极,所以子進(jìn)程不會(huì)是進(jìn)程組長(zhǎng)和會(huì)話期組長(zhǎng)
2.脫離控制終端窄瘟。通過setid方法,使子進(jìn)程成為新的會(huì)話期組長(zhǎng)趟卸,由于該會(huì)話期只有一個(gè)蹄葱,所以該子進(jìn)程也是進(jìn)程組長(zhǎng)。這時(shí)該會(huì)話期組長(zhǎng)是沒有可控制終端的
3.禁止進(jìn)程重新打開控制終端〕校現(xiàn)在图云,進(jìn)程已經(jīng)成為無終端的會(huì)話組長(zhǎng),但它可以重新打開一個(gè)控制終端邻邮】⒖觯可以使進(jìn)程不再成為會(huì)話組長(zhǎng)來禁止進(jìn)程重新打開控制終端:
4.關(guān)閉打開的文件描述符
5.改變當(dāng)前工作目錄。(進(jìn)程活動(dòng)時(shí)筒严,其工作目錄所在的文件系統(tǒng)不能卸下丹泉。一般需要將工作目錄改變到根目錄)
6.重設(shè)文件創(chuàng)建掩碼(進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了文件創(chuàng)建掩碼。它可能修改守護(hù)進(jìn)程所創(chuàng)建的文件的存取權(quán)限鸭蛙。為防止這一點(diǎn)摹恨,將文件創(chuàng)建掩碼清除)
7.從子進(jìn)程中fork另一個(gè)子進(jìn)程,該子進(jìn)程不是進(jìn)程組長(zhǎng)娶视,也不是會(huì)話期組長(zhǎng)晒哄,是真正的守護(hù)進(jìn)程
程序?qū)崿F(xiàn):
#coding: utf-8
import sys, os
'''
將當(dāng)前進(jìn)程fork為一個(gè)守護(hù)進(jìn)程
注意:如果你的守護(hù)進(jìn)程是由inetd啟動(dòng)的,不要這樣做歇万!inetd完成了
所有需要做的事情揩晴,包括重定向標(biāo)準(zhǔn)文件描述符,需要做的事情只有chdir()和umask()了
'''
def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
#重定向標(biāo)準(zhǔn)文件描述符(默認(rèn)情況下定向到/dev/null)
try:
pid = os.fork()
#父進(jìn)程(會(huì)話組頭領(lǐng)進(jìn)程)退出贪磺,這意味著一個(gè)非會(huì)話組頭領(lǐng)進(jìn)程永遠(yuǎn)不能重新獲得控制終端硫兰。
if pid > 0:
sys.exit(0) #父進(jìn)程退出
except OSError, e:
sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) )
sys.exit(1)
#從母體環(huán)境脫離
os.chdir("/") #chdir確認(rèn)進(jìn)程不保持任何目錄于使用狀態(tài),否則不能umount一個(gè)文件系統(tǒng)寒锚。也可以改變到對(duì)于守護(hù)程序運(yùn)行重要的文件所在目錄
os.umask(0) #調(diào)用umask(0)以便擁有對(duì)于寫的任何東西的完全控制劫映,因?yàn)橛袝r(shí)不知道繼承了什么樣的umask违孝。
os.setsid() #setsid調(diào)用成功后,進(jìn)程成為新的會(huì)話組長(zhǎng)和新的進(jìn)程組長(zhǎng)泳赋,并與原來的登錄會(huì)話和進(jìn)程組脫離雌桑。
#執(zhí)行第二次fork
try:
pid = os.fork()
if pid > 0:
sys.exit(0) #第二個(gè)父進(jìn)程退出
except OSError, e:
sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) )
sys.exit(1)
#進(jìn)程已經(jīng)是守護(hù)進(jìn)程了,重定向標(biāo)準(zhǔn)文件描述符
for f in sys.stdout, sys.stderr: f.flush()
si = open(stdin, 'r')
so = open(stdout, 'a+')
se = open(stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno()) #dup2函數(shù)原子化關(guān)閉和復(fù)制文件描述符
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
#示例函數(shù):每秒打印一個(gè)數(shù)字和時(shí)間戳
def main():
import time
sys.stdout.write('Daemon started with pid %d\n' % os.getpid())
sys.stdout.write('Daemon stdout output\n')
sys.stderr.write('Daemon stderr output\n')
c = 0
while True:
sys.stdout.write('%d: %s\n' %(c, time.ctime()))
sys.stdout.flush()
c = c+1
time.sleep(1)
if __name__ == "__main__":
daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log')
main()