Linux Daemon(守護進程)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。它不須要用戶輸入就能運行並且提供某種服務,不是對整個系統就是對某個用戶程序提供服務。Linux系統的大多數服務器就是經過守護進程實現的。常見的守護進程包括系統日誌進程syslogd、 web服務器httpd、郵件服務器sendmail和數據庫服務器mysqld等。html
首先咱們要了解一些基本概念:python
會話期:mysql
會話期(session)是一個或多個進程組的集合,當集合中只有一個進程組時,sid與該進程組 組長的pid相同,這些進程組共享一個控制終端。這個控制終端一般是建立進程的登陸終端,控制終端,登陸會話和進程組一般是從父進程繼承下來的。linux
setsid()函數能夠創建一個對話期:web
若是,調用setsid的進程不是一個進程組的組長,此函數建立一個新的會話期;若是是該進程組的組長,則此函數返回錯誤sql
(1)此進程變成該對話期的首進程shell
(2)此進程變成一個新進程組的組長進程數據庫
(3)此進程沒有控制終端,若是在調用setsid前,該進程有控制終端,那麼與該終端的聯繫被解除服務器
(4)爲了保證這一點,咱們先調用fork()而後exit(),此時只有子進程在運行,fork後的子進程pid是從新分配的,即保證了此時運行的進程永遠不會是進程組的組長session
如今咱們來給出建立守護進程所需步驟:
編寫守護進程的通常步驟步驟:
(1)在父進程中執行fork並exit推出;
(2)在子進程中調用setsid函數建立新的會話;
(3)在子進程中調用chdir函數,讓根目錄 」/」 成爲子進程的工做目錄;
(4)在子進程中調用umask函數,設置進程的umask爲0;
(5)在子進程中關閉任何不須要的文件描述符
以圖解兩次fork的過程:
前提,會話中只有一個進程組,進程組中只有一個進程;
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
100 | 10 | 100 | 100 | y |
第一次fork時:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
100 | 10 | 100 | 100 | y |
101 | 100 | 100(extends parent) | 100 | n |
exit父進程:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
101 | 100 | 100 | 100 | n |
init進程接管:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
101 | 1 | 100 | 100 | n |
執行setsid後:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
101 | 1 | 101 | 101 | y |
第二次fork:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
101 | 1 | 101 | 101 | y |
102 | 101 | 101 | 101 | n |
exit父進程:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
102 | 101 | 101 | 101 | n |
由init進程接管:
pid | ppid | pgid(組id) | sid(session id) | 組長(y/n) |
102 | 1 | 101 | 101 | n |
完成上面的4個步驟,那麼最終的孫子進程就稱爲守護進程。先看下代碼,後面再分析下每一個步驟的緣由。
#!/usr/bin/env python #coding=utf8 import os, sys, time #產生子進程,然後父進程退出 pid = os.fork() if pid > 0: sys.exit(0) #修改子進程工做目錄 os.chdir("/") #建立新的會話,子進程成爲會話的首進程 os.setsid() #修改工做目錄的umask os.umask(0) #建立孫子進程,然後子進程退出 pid = os.fork() if pid > 0: sys.exit(0) #重定向標準輸入流、標準輸出流、標準錯誤 sys.stdout.flush() sys.stderr.flush() si = file("/dev/null", 'r') so = file("/dev/null", 'a+') se = file("/dev/null", 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) #孫子進程的程序內容 while True: time.sleep(10) f = open('/home/test.txt', 'a') f.write('hello')
上面的程序沒有任何錯誤處理,可是不影響原理分析。若是要應用到項目裏,還需完善。下面筆者談下本身對每一個步驟的理解。
一、fork子進程,父進程退出
一般,咱們執行服務端程序的時候都會經過終端鏈接到服務器,成功鏈接後會加載shell環境,終端和shell都是進程,shell進程是終端進程的子進程,經過ps命令能夠很容易的查看到。在這個shell環境下一開始執行的程序都是shell進程的子進程,天然會受到shell進程的影響。在程序裏fork子進程後,父進程退出,對了shell進程來講,這個父進程就算執行完了,而產生的子進程會被init進程接管,從而也就脫離了終端的控制。
二、關閉打開的文件描述符和修改子進程的工做目錄
進程從建立它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,形成進程所在的文件系統沒法卸下以及引發沒法預料的錯誤,子進程在建立的時候會繼承父進程的工做目錄,若是執行的程序是在u盤裏的,就會致使u盤不能卸載。
三、建立新會話
使用setsid後,子進程就會成爲新會話的首進程(session leader);子進程會成爲新進程組的組長進程;子進程沒有控制終端。
四、修改umask
因爲umask會屏蔽權限,因此設定爲0,這樣能夠避免讀寫文件時碰到權限問題。
五、fork孫子進程,子進程退出
通過上面幾個步驟後,子進程會成爲新的進程組老大,能夠從新申請打開終端,爲了不這個問題,fork孫子進程出來。
六、重定向孫子進程的標準輸入流、標準輸出流、標準錯誤流到/dev/null
由於是守護進程,自己已經脫離了終端,那麼標準輸入流、標準輸出流、標準錯誤流就沒有什麼意義了。因此都轉向到/dev/null,就是都丟棄的意思。
7. 處理SIGCHLD信號
處理SIGCHLD信號並非必須的。但對於某些進程,特別是服務器進程每每在請求到來時生成子進程處理請求。若是父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。若是父進程等待子進程結束,將增長父進程的負擔,影響服務器進程的併發性能。在Linux下能夠簡單地將SIGCHLD信號的操做設爲SIG_IGN。
signal(SIGCHLD,SIG_IGN);
參考資料:
http://www.01happy.com/linux-python-daemon/
http://www.cnblogs.com/mickole/p/3188321.html
http://blog.csdn.net/jason314/article/details/5640969
針對守護這個概念延伸到python,python也有守護線程的概念
python daemon理解:
守護進程只與主進程有相互做用關係;主線程結束爲前提,守護進程保證全部的子線程都執行完後就所有退出,即便守護進程沒有執行完進程也會退出;
參考資料:
http://www.dongwm.com/archives/guanyuthreadingyanjiuer/