概念
守護(hù)進(jìn)程(Daemon)也稱(chēng)為精靈進(jìn)程是一種生存期較長(zhǎng)的一種進(jìn)程拷泽。它們獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件崎弃。他們常常在系統(tǒng)引導(dǎo)裝入時(shí)啟動(dòng),在系統(tǒng)關(guān)閉時(shí)終止。unix系統(tǒng)有很多守護(hù)進(jìn)程襟诸,大多數(shù)服務(wù)器都是用守護(hù)進(jìn)程實(shí)現(xiàn)的,例如inetd守護(hù)進(jìn)程基协。
需要了解的相關(guān)概念
- 進(jìn)程 (process)
- 進(jìn)程組 (process group)
- 會(huì)話 (session)
可參考以下博文
實(shí)現(xiàn)原理
參考 APUE關(guān)于守護(hù)進(jìn)程的章節(jié)
大致流程如下:
- 后臺(tái)運(yùn)行
首次fork歌亲,創(chuàng)建父-子進(jìn)程,使父進(jìn)程退出
- 脫離控制終端澜驮,登錄會(huì)話和進(jìn)程組
通過(guò)setsid使子進(jìn)程成為process group leader陷揪、session leader
- 禁止進(jìn)程重新打開(kāi)控制終端
二次fork,創(chuàng)建子-孫進(jìn)程杂穷,使sid不等pid
- 關(guān)閉打開(kāi)的文件描述符
通常就關(guān)閉STDIN悍缠、STDOUT和STDERR
- 改變當(dāng)前工作目錄
防止占用別的路徑的working dir的fd,導(dǎo)致一些block不能unmount
- 重設(shè)umask
防止后續(xù)子進(jìn)程繼承非默認(rèn)umask造成奇怪的行為
- 處理SIGCHLD信號(hào)
非必需
- 日志
輸出重定向后耐量,需要有機(jī)制放映內(nèi)部情況
關(guān)于兩次fork
第二個(gè)fork不是必須的飞蚓,只是為了防止進(jìn)程打開(kāi)控制終端。
打開(kāi)一個(gè)控制終端的條件是該進(jìn)程必須是session leader廊蜒。第一次fork趴拧,setsid之后溅漾,子進(jìn)程成為session leader,進(jìn)程可以打開(kāi)終端著榴;第二次fork產(chǎn)生的進(jìn)程添履,不再是session leader,進(jìn)程則無(wú)法打開(kāi)終端兄渺。
也就是說(shuō)缝龄,只要程序?qū)崿F(xiàn)得好,控制程序不主動(dòng)打開(kāi)終端挂谍,無(wú)第二次fork亦可叔壤。
代碼實(shí)現(xiàn)
# coding: utf-8
import os
import sys
import time
import atexit
import signal
class Daemon:
def __init__(self, pidfile='/tmp/daemon.pid', stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self):
if os.path.exists(self.pidfile):
raise RuntimeError('Already running.')
# First fork (detaches from parent)
try:
if os.fork() > 0:
raise SystemExit(0)
except OSError as e:
raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror))
os.chdir('/')
os.setsid()
os.umask(0o22)
# Second fork (relinquish session leadership)
try:
if os.fork() > 0:
raise SystemExit(0)
except OSError as e:
raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror))
# Flush I/O buffers
sys.stdout.flush()
sys.stderr.flush()
# Replace file descriptors for stdin, stdout, and stderr
with open(self.stdin, 'rb', 0) as f:
os.dup2(f.fileno(), sys.stdin.fileno())
with open(self.stdout, 'ab', 0) as f:
os.dup2(f.fileno(), sys.stdout.fileno())
with open(self.stderr, 'ab', 0) as f:
os.dup2(f.fileno(), sys.stderr.fileno())
# Write the PID file
with open(self.pidfile, 'w') as f:
print(os.getpid(), file=f)
# Arrange to have the PID file removed on exit/signal
atexit.register(lambda: os.remove(self.pidfile))
signal.signal(signal.SIGTERM, self.__sigterm_handler)
# Signal handler for termination (required)
@staticmethod
def __sigterm_handler(signo, frame):
raise SystemExit(1)
def start(self):
try:
self.daemonize()
except RuntimeError as e:
print(e, file=sys.stderr)
raise SystemExit(1)
self.run()
def stop(self):
try:
if os.path.exists(self.pidfile):
with open(self.pidfile) as f:
os.kill(int(f.read()), signal.SIGTERM)
else:
print('Not running.', file=sys.stderr)
raise SystemExit(1)
except OSError as e:
if 'No such process' in str(e) and os.path.exists(self.pidfile):
os.remove(self.pidfile)
def restart(self):
self.stop()
self.start()
def run(self):
pass
使用測(cè)試
import os
import sys
import time
from daemon import Daemon
class MyTestDaemon(Daemon):
def run(self):
sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid()))
while True:
sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime()))
sys.stdout.flush()
time.sleep(5)
if __name__ == '__main__':
PIDFILE = '/tmp/daemon-example.pid'
LOG = '/tmp/daemon-example.log'
daemon = MyTestDaemon(pidfile=PIDFILE, stdout=LOG, stderr=LOG)
if len(sys.argv) != 2:
print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)
raise SystemExit(1)
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr)
raise SystemExit(1)
[daemon] python test.py start 23:45:42
[daemon] cat /tmp/daemon-example.pid 23:45:49
8532
[daemon] ps -ef|grep 8532 | grep -v grep 23:46:07
502 8532 1 0 11:45下午 ?? 0:00.00 python test.py start
[daemon] tail -f /tmp/daemon-example.log 23:46:20
Daemon started with pid 8532
Daemon Alive! Fri Dec 2 23:45:49 2016
Daemon Alive! Fri Dec 2 23:45:54 2016
Daemon Alive! Fri Dec 2 23:45:59 2016
Daemon Alive! Fri Dec 2 23:46:04 2016
Daemon Alive! Fri Dec 2 23:46:09 2016
Daemon Alive! Fri Dec 2 23:46:14 2016
Daemon Alive! Fri Dec 2 23:46:19 2016
Daemon Alive! Fri Dec 2 23:46:24 2016
Daemon Alive! Fri Dec 2 23:46:29 2016
Daemon Alive! Fri Dec 2 23:46:34 2016
[daemon] python test.py stop 23:46:36
[daemon] ps -ef|grep 8532 | grep -v grep 23:46:43
也可以使用 Supervisor 管理進(jìn)程,具體可看 Supervisor安裝與配置
參考
- tzuryby/daemon.py python2實(shí)現(xiàn)的通用的python daemon類(lèi)
- 12.14 在Unix系統(tǒng)上面啟動(dòng)守護(hù)進(jìn)程 python3實(shí)現(xiàn)的daemon