Python 實現的守護進程
守護進程:通常被定義為一個后臺進程,而且它不屬于任何一個終端會話(terminal session)。許多系統服務由守護程序實施;如網絡服務,打印等。
下面是轉自一位網友寫的編寫守護進程的步驟:
1. 調用fork()以便父進程可以退出,這樣就將控制權歸還給運行你程序的命令行或shell程序。需要這一步以便保證新進程不是一個進程組頭領進程(process group leader)。下一步,‘setsid()’,會因為你是進程組頭領進程而失敗。
2. 調用‘setsid()’ 以便成為一個進程組和會話組的頭領進程。由于一個控制終端與一個會話相關聯,而且這個新會話還沒有獲得一個控制終端,我們的進程沒有控制終端,這對于守護程序來說是一件好事。
3. 再次調用‘fork()’所以父進程(會話組頭領進程)可以退出。這意味著我們,一個非會話組頭領進程永遠不能重新獲得控制終端。
4. 調用‘chdir("/")’確認我們的進程不保持任何目錄于使用狀態。不做這個會導致系統管理員不能卸裝(umount)一個文件系統,因為它是我們的當前工作目錄。 [類似的,我們可以改變當前目錄至對于守護程序運行重要的文件所在目錄]
5. 調用‘umask(0)’以便我們擁有對于我們寫的任何東西的完全控制。我們不知道我們繼承了什么樣的umask。 [這一步是可選的](譯者注:這里指步驟5,因為守護程序不一定需要寫文件)
6. 調用‘close()’關閉文件描述符0,1和2。這樣我們釋放了從父進程繼承的標準輸入,標準輸出,和標準錯誤輸出。我們沒辦法知道這些文描述符符可能 已經被重定向去哪里。注意到許多守護程序使用‘sysconf()’來確認‘_SC_OPEN_MAX’的限制。‘_SC_OPEN_MAX’告訴你每個 進程能夠打開的最多文件數。然后使用一個循環,守護程序可以關閉所有可能的文件描述符。你必須決定你需要做這個或不做。如果你認為有可能有打開的文件描述 符,你需要關閉它們,因為系統有一個同時打開文件數的限制。
7. 為標準輸入,標準輸出和標準錯誤輸出建立新的文件描述符。即使你不打算使用它們,打開著它們不失為一個好主意。準確操作這些描述符是基于各自愛好;比如 說,如果你有一個日志文件,你可能希望把它作為標準輸出和標準錯誤輸出打開,而把‘/dev/null’作為標準輸入打開;作為替代方法,你可以將‘ /dev/console’作為標準錯誤輸出和/或標準輸出打開,而‘/dev/null’作為標準輸入,或者任何其它對你的守護程序有意義的結合方法。 (譯者注:一般使用dup2函數原子化關閉和復制文件描述符。
說實話,上面這段文字看著有點云里霧里,下面看個具體的代碼(我只粘貼了函數的第一部分,也是最重要的一部分,要查看整個代碼,請移步到這 http://www.pythonid.com/bbs/redirect.php?tid=239&goto=lastpost& highlight=自行查看):
def daemonize(stdout='/dev/null', stderr=None, stdin='/dev/null',
pidfile=None, startmsg = 'started with pid %s' ):
'''''
This forks the current process into a daemon.
The stdin, stdout, and stderr arguments are file names that
will be opened and be used to replace the standard file descriptors
in sys.stdin, sys.stdout, and sys.stderr.
These arguments are optional and default to /dev/null.
Note that stderr is opened unbuffered, so
if it shares a file with stdout then interleaved output
may not appear in the order that you expect.
'''
# flush io
sys.stdout.flush()
sys.stderr.flush()
# Do first fork.
try:
pid = os.fork()
if pid > 0: sys.exit(0) # Exit first parent.
except OSError, e:
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
# Decouple from parent environment.
os.chdir("/")
os.umask(0)
os.setsid()
# Do second fork.
try:
pid = os.fork()
if pid > 0: sys.exit(0) # Exit second parent.
except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
# Open file descriptors and print start message
if not stderr: stderr = stdout
si = file(stdin, 'r')
so = file(stdout, 'a+')
se = file(stderr, 'a+', 0) #unbuffered
pid = str(os.getpid())
sys.stderr.write("\n%s\n" % startmsg % pid)
sys.stderr.flush()
if pidfile: file(pidfile,'w+').write("%s\n" % pid)
# Redirect standard file descriptors.
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno()) 仔細比對前面說的步驟和代碼,大體上是沒有什么問題了,但是疑問就來了,os.fork()到底是怎么工作的呢,GOOGLE了個遍,最后的結論是這樣:
父進程執行代碼到os.fork()處時,會將自己整個拷貝一份(即子進程)這時候父進程os.fork()的返回值大于零(即子進程的PID), 子進程os.fork()的返回值等于零,父進程結束,子進程繼續執行,這時候又遇到第二個os.fork(),如上次一樣,原來的子進程變成了父進程, 又產生新的子進程,之后父進程就結束。這就能夠說通第一次是避免process group leader,第二次是避免session group leader。子進程就變成了一個五終端,無會話的完全自我掌控的后臺進程了。
文章出處:http://www.iteye.com/topic/610727