參考:
如何創(chuàng)建一個(gè)進(jìn)程
實(shí)際上敢靡,當(dāng)計(jì)算機(jī)開機(jī)的時(shí)候挂滓,內(nèi)核(kernel)只建立了一個(gè)init進(jìn)程。Linux內(nèi)核并不提供直接建立新進(jìn)程的系統(tǒng)調(diào)用啸胧。剩下的所有進(jìn)程都是init進(jìn)程通過fork機(jī)制建立的赶站。新的進(jìn)程要通過老的進(jìn)程復(fù)制自身得到,這就是fork纺念。fork是一個(gè)系統(tǒng)調(diào)用贝椿。進(jìn)程存活于內(nèi)存中。每個(gè)進(jìn)程都在內(nèi)存中分配有屬于自己的一片空間 (address space)陷谱。當(dāng)進(jìn)程fork的時(shí)候烙博,Linux在內(nèi)存中開辟出一片新的內(nèi)存空間給新的進(jìn)程瑟蜈,并將老的進(jìn)程空間中的內(nèi)容復(fù)制到新的空間中,此后兩個(gè)進(jìn)程同時(shí)運(yùn)行渣窜。
老進(jìn)程成為新進(jìn)程的父進(jìn)程(parent process)铺根,而相應(yīng)的,新進(jìn)程就是老的進(jìn)程的子進(jìn)程(child process)乔宿。一個(gè)進(jìn)程除了有一個(gè)PID之外位迂,還會(huì)有一個(gè)PPID(parent PID)來存儲(chǔ)的父進(jìn)程PID。如果我們循著PPID不斷向上追溯的話详瑞,總會(huì)發(fā)現(xiàn)其源頭是init進(jìn)程掂林。所以說,所有的進(jìn)程也構(gòu)成一個(gè)以init為根的樹狀結(jié)構(gòu)蛤虐。
ork通常作為一個(gè)函數(shù)被調(diào)用党饮。這個(gè)函數(shù)會(huì)有兩次返回,將子進(jìn)程的PID返回給父進(jìn)程驳庭,0返回給子進(jìn)程刑顺。實(shí)際上,子進(jìn)程總可以查詢自己的PPID來知道自己的父進(jìn)程是誰饲常,這樣蹲堂,一對(duì)父進(jìn)程和子進(jìn)程就可以隨時(shí)查詢對(duì)方。
通常在調(diào)用fork函數(shù)之后贝淤,程序會(huì)設(shè)計(jì)一個(gè)if選擇結(jié)構(gòu)柒竞。當(dāng)PID等于0時(shí),說明該進(jìn)程為子進(jìn)程播聪,那么讓它執(zhí)行某些指令,比如說使用exec庫函數(shù)(library function)讀取另一個(gè)程序文件朽基,并在當(dāng)前的進(jìn)程空間執(zhí)行 (這實(shí)際上是我們使用fork的一大目的: 為某一程序創(chuàng)建進(jìn)程);而當(dāng)PID為一個(gè)正整數(shù)時(shí)离陶,說明為父進(jìn)程稼虎,則執(zhí)行另外一些指令。由此招刨,就可以在子進(jìn)程建立之后霎俩,讓它執(zhí)行與父進(jìn)程不同的功能。
守護(hù)進(jìn)程編寫思路
詳細(xì)參見: 《AdvancedProgrammingin The Unix Environment》Section 13.3 Page 583
1沉眶、調(diào)用umask將文件模式創(chuàng)建屏蔽字設(shè)置為一個(gè)已知值(通常是0)打却。如前所述,由繼承得來的文件模式創(chuàng)建屏蔽字可能會(huì)被設(shè)置為拒絕權(quán)限谎倔。我們可以根據(jù)我們的具體需求設(shè)定特定的權(quán)限柳击。
2、調(diào)用fork片习,然后使父進(jìn)程exit腻暮。這樣做彤守,使得當(dāng)我們以./的shell命令啟動(dòng)守護(hù)進(jìn)程時(shí),父進(jìn)程終止會(huì)讓shell認(rèn)為此命令已經(jīng)執(zhí)行完畢哭靖,而且,這也使子進(jìn)程獲得了一個(gè)新的進(jìn)程ID侈离。此外试幽,讓父進(jìn)程先于子進(jìn)程exit,會(huì)使子進(jìn)程變?yōu)楣聝哼M(jìn)程卦碾,這樣子進(jìn)程成功被init這個(gè)用戶級(jí)守護(hù)進(jìn)程收養(yǎng)铺坞。
3、調(diào)用setsid創(chuàng)建一個(gè)新會(huì)話洲胖。這在setsid函數(shù)中有介紹济榨,調(diào)用setsid,會(huì)使這個(gè)子進(jìn)程成為(a)新會(huì)話的首進(jìn)程绿映,(b)成為一個(gè)新進(jìn)程組的組長進(jìn)程擒滑,(c)切斷其與控制終端的聯(lián)系,或者就是沒有控制終端叉弦。至此丐一,這個(gè)子進(jìn)程作為新的進(jìn)程組的組長,完全脫離了其他進(jìn)程的控制淹冰,并且沒有控制終端库车。
4、將當(dāng)前工作目錄更改為根目錄(或某一特定目錄位置)樱拴。這是為了保證守護(hù)進(jìn)程的當(dāng)前工作目錄在一個(gè)掛載的文件系統(tǒng)中柠衍,該文件系統(tǒng)不能被卸載。
5晶乔、關(guān)閉不再需要的文件描述符珍坊。根據(jù)具體情況來定。
6瘪弓、某些守護(hù)進(jìn)程可以打開/dev/null使其具有文件描述符0垫蛆、1、2腺怯,這使任何一個(gè)試圖讀標(biāo)準(zhǔn)輸入袱饭、寫標(biāo)準(zhǔn)輸出或標(biāo)準(zhǔn)錯(cuò)誤的庫例程都不會(huì)產(chǎn)生任何效果。
7呛占、忽略SIGCHLD信號(hào)
這一步并非必須的虑乖,只對(duì)需要?jiǎng)?chuàng)建子進(jìn)程的守護(hù)進(jìn)程才有必要,很多服務(wù)器守護(hù)進(jìn)程設(shè)計(jì)成通過派生子進(jìn)程來處理客戶端的請(qǐng)求晾虑,如果父進(jìn)程不對(duì)SIGCHLD信號(hào)進(jìn)行處理的話疹味,子進(jìn)程在終止后變成僵尸進(jìn)程仅叫,通過將信號(hào)SIGCHLD的處理方式設(shè)置為SIG_IGN可以避免這種情況發(fā)生。
8糙捺、用日志系統(tǒng)記錄出錯(cuò)信息
因?yàn)槭刈o(hù)進(jìn)程沒有控制終端诫咱,當(dāng)進(jìn)程出現(xiàn)錯(cuò)誤時(shí)無法寫入到標(biāo)準(zhǔn)輸出上,可以通過調(diào)用syslog將出錯(cuò)信息寫入到指定的文件中洪灯。該接口函數(shù)包括openlog坎缭、syslog、closelog签钩、setlogmask掏呼,具體可參考13.4節(jié)出錯(cuò)記錄。
9铅檩、守護(hù)進(jìn)程退出處理
當(dāng)用戶需要外部停止守護(hù)進(jìn)程運(yùn)行時(shí)憎夷,往往會(huì)使用 kill命令停止該守護(hù)進(jìn)程。所以昧旨,守護(hù)進(jìn)程中需要編碼來實(shí)現(xiàn)kill發(fā)出的signal信號(hào)處理拾给,達(dá)到進(jìn)程的正常退出。
總結(jié)守護(hù)進(jìn)程編程規(guī)則
- 1.在后臺(tái)運(yùn)行臼予,調(diào)用fork 鸣戴,然后使父進(jìn)程exit
- 2.脫離控制終端,登錄會(huì)話和進(jìn)程組粘拾,調(diào)用setsid()使進(jìn)程成為會(huì)話組長
- 3.禁止進(jìn)程重新打開控制終端
- 4.關(guān)閉打開的文件描述符窄锅,調(diào)用fclose()
- 5.將當(dāng)前工作目錄更改為根目錄。
- 6.重設(shè)文件創(chuàng)建掩碼為0
- 7.處理SIGCHLD 信號(hào)
Python代碼實(shí)現(xiàn)
#!/usr/bin/env python
# coding:utf-8
import os,sys,time
def daemon_init(stdin='/dev/null',stdout='/dev/null',stderr='/dev/null'):
sys.stdin = open(stdin,'r')
sys.stdout = open(stdout,'a+')
sys.stderr = open(stderr,'a+')
try:
pid = os.fork()
if pid > 0: # judge if pid is parent id
os._exit(0) # kill parent id
except OSError as e:
sys.stderr.write("first fork failed!!"+e.strerror)
os._exit(1)
# 子進(jìn)程缰雇, 由于父進(jìn)程已經(jīng)退出入偷,所以子進(jìn)程變?yōu)楣聝哼M(jìn)程,由init收養(yǎng)
'''setsid使子進(jìn)程成為新的會(huì)話首進(jìn)程械哟,和進(jìn)程組的組長疏之,與原來的進(jìn)程組、控制終端和登錄會(huì)話脫離暇咆。'''
os.setsid()
'''防止在類似于臨時(shí)掛載的文件系統(tǒng)下運(yùn)行锋爪,例如/mnt文件夾下,這樣守護(hù)進(jìn)程一旦運(yùn)行爸业,臨時(shí)掛載的文件系統(tǒng)就無法卸載了其骄,這里我們推薦把當(dāng)前工作目錄切換到根目錄下'''
os.chdir("/")
'''設(shè)置用戶創(chuàng)建文件的默認(rèn)權(quán)限,設(shè)置的是權(quán)限“補(bǔ)碼”扯旷,這里將文件權(quán)限掩碼設(shè)為0拯爽,使得用戶創(chuàng)建的文件具有最大的權(quán)限。否則钧忽,默認(rèn)權(quán)限是從父進(jìn)程繼承得來的'''
os.umask(0)
try:
pid = os.fork() #第二次進(jìn)行fork,為了防止會(huì)話首進(jìn)程意外獲得控制終端
if pid>0:
os._exit(0) #父進(jìn)程退出
except OSError as e:
sys.stderr.write("second fork failed"+e.strerror)
sys.stdout.write("Daemon has been created! with pid: %d\n" % os.getpid())
sys.stdout.flush() #由于這里我們使用的是標(biāo)準(zhǔn)IO毯炮,回顧APUE第五章逼肯,這里應(yīng)該是行緩沖或全緩沖,因此要調(diào)用flush桃煎,從內(nèi)存中刷入日志文件篮幢。
def main():
print '========main function start!============' #在調(diào)用daemon_init函數(shù)前是可以使用print到標(biāo)準(zhǔn)輸出的,調(diào)用之后就要用把提示信息通過stdout發(fā)送到日志系統(tǒng)中了
daemon_init('/dev/null','/tmp/daemon.log','/tmp/daemon.err') # 調(diào)用之后为迈,你的程序已經(jīng)成為了一個(gè)守護(hù)進(jìn)程洲拇,可以執(zhí)行自己的程序入口了
time.sleep(10) #daemon化自己的程序之后,sleep 10秒曲尸,模擬阻塞
if __name__ == '__main__':
main()