概念
守护进程(Daemon)也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用守护进程实现的,例如inetd守护进程。
需要了解的相关概念
- 进程 (process)
- 进程组 (process group)
- 会话 (session)
可参考以下博文
实现原理
大致流程如下:
-
后台运行
首次fork,创建父-子进程,使父进程退出
-
脱离控制终端,登录会话和进程组
通过setsid使子进程成为process group leader、session leader
-
禁止进程重新打开控制终端
二次fork,创建子-孙进程,使sid不等pid
-
关闭打开的文件描述符
通常就关闭STDIN、STDOUT和STDERR
-
改变当前工作目录
防止占用别的路径的working dir的fd,导致一些block不能unmount
-
重设umask
防止后续子进程继承非默认umask造成奇怪的行为
-
处理SIGCHLD信号
非必需
-
日志
输出重定向后,需要有机制放映内部情况
关于两次fork
第二个fork不是必须的,只是为了防止进程打开控制终端。
打开一个控制终端的条件是该进程必须是session leader。第一次fork,setsid之后,子进程成为session leader,进程可以打开终端;第二次fork产生的进程,不再是session leader,进程则无法打开终端。
也就是说,只要程序实现得好,控制程序不主动打开终端,无第二次fork亦可。
代码实现
1# coding: utf-8 2 3import os 4import sys 5import time 6import atexit 7import signal 8 9 10class Daemon: 11 def __init__(self, pidfile='/tmp/daemon.pid', stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 12 self.stdin = stdin 13 self.stdout = stdout 14 self.stderr = stderr 15 self.pidfile = pidfile 16 17 def daemonize(self): 18 if os.path.exists(self.pidfile): 19 raise RuntimeError('Already running.') 20 21 # First fork (detaches from parent) 22 try: 23 if os.fork() > 0: 24 raise SystemExit(0) 25 except OSError as e: 26 raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror)) 27 28 os.chdir('/') 29 os.setsid() 30 os.umask(0o22) 31 32 # Second fork (relinquish session leadership) 33 try: 34 if os.fork() > 0: 35 raise SystemExit(0) 36 except OSError as e: 37 raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror)) 38 39 # Flush I/O buffers 40 sys.stdout.flush() 41 sys.stderr.flush() 42 43 # Replace file descriptors for stdin, stdout, and stderr 44 with open(self.stdin, 'rb', 0) as f: 45 os.dup2(f.fileno(), sys.stdin.fileno()) 46 with open(self.stdout, 'ab', 0) as f: 47 os.dup2(f.fileno(), sys.stdout.fileno()) 48 with open(self.stderr, 'ab', 0) as f: 49 os.dup2(f.fileno(), sys.stderr.fileno()) 50 51 # Write the PID file 52 with open(self.pidfile, 'w') as f: 53 print(os.getpid(), file=f) 54 55 # Arrange to have the PID file removed on exit/signal 56 atexit.register(lambda: os.remove(self.pidfile)) 57 58 signal.signal(signal.SIGTERM, self.__sigterm_handler) 59 60 # Signal handler for termination (required) 61 @staticmethod 62 def __sigterm_handler(signo, frame): 63 raise SystemExit(1) 64 65 def start(self): 66 try: 67 self.daemonize() 68 except RuntimeError as e: 69 print(e, file=sys.stderr) 70 raise SystemExit(1) 71 72 self.run() 73 74 def stop(self): 75 try: 76 if os.path.exists(self.pidfile): 77 with open(self.pidfile) as f: 78 os.kill(int(f.read()), signal.SIGTERM) 79 else: 80 print('Not running.', file=sys.stderr) 81 raise SystemExit(1) 82 except OSError as e: 83 if 'No such process' in str(e) and os.path.exists(self.pidfile): 84 os.remove(self.pidfile) 85 86 def restart(self): 87 self.stop() 88 self.start() 89 90 def run(self): 91 pass
使用测试
1import os 2import sys 3import time 4 5from daemon import Daemon 6 7class MyTestDaemon(Daemon): 8 def run(self): 9 sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid())) 10 while True: 11 sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime())) 12 sys.stdout.flush() 13 14 time.sleep(5) 15 16if __name__ == '__main__': 17 PIDFILE = '/tmp/daemon-example.pid' 18 LOG = '/tmp/daemon-example.log' 19 daemon = MyTestDaemon(pidfile=PIDFILE, stdout=LOG, stderr=LOG) 20 21 if len(sys.argv) != 2: 22 print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr) 23 raise SystemExit(1) 24 25 if 'start' == sys.argv[1]: 26 daemon.start() 27 elif 'stop' == sys.argv[1]: 28 daemon.stop() 29 elif 'restart' == sys.argv[1]: 30 daemon.restart() 31 else: 32 print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr) 33 raise SystemExit(1) 34 35[daemon] python test.py start 23:45:42 36[daemon] cat /tmp/daemon-example.pid 23:45:49 378532 38[daemon] ps -ef|grep 8532 | grep -v grep 23:46:07 39 502 8532 1 0 11:45下午 ?? 0:00.00 python test.py start 40[daemon] tail -f /tmp/daemon-example.log 23:46:20 41Daemon started with pid 8532 42Daemon Alive! Fri Dec 2 23:45:49 2016 43Daemon Alive! Fri Dec 2 23:45:54 2016 44Daemon Alive! Fri Dec 2 23:45:59 2016 45Daemon Alive! Fri Dec 2 23:46:04 2016 46Daemon Alive! Fri Dec 2 23:46:09 2016 47Daemon Alive! Fri Dec 2 23:46:14 2016 48Daemon Alive! Fri Dec 2 23:46:19 2016 49Daemon Alive! Fri Dec 2 23:46:24 2016 50Daemon Alive! Fri Dec 2 23:46:29 2016 51Daemon Alive! Fri Dec 2 23:46:34 2016 52 53[daemon] python test.py stop 23:46:36 54[daemon] ps -ef|grep 8532 | grep -v grep 23:46:43
如果有工程需求,可以使用 python-daemon 库
参考博文
- tzuryby/daemon.py python2实现的通用的python daemon类
- 12.14 在Unix系统上面启动守护进程 python3实现的daemon