============================ 12.14 在Unix系统上é¢å¯åŠ¨å®ˆæŠ¤è¿›ç¨‹ ============================ ---------- 问题 ---------- ä½ æƒ³ç¼–å†™ä¸€ä¸ªä½œä¸ºä¸€ä¸ªåœ¨Unix或类Unix系统上é¢è¿è¡Œçš„守护进程è¿è¡Œçš„程åºã€‚ ---------- 解决方案 ---------- 创建一个æ£ç¡®çš„守护进程需è¦ä¸€ä¸ªç²¾ç¡®çš„系统调用åºåˆ—以åŠå¯¹äºŽç»†èŠ‚的控制。 下é¢çš„代ç å±•ç¤ºäº†æ€Žæ ·å®šä¹‰ä¸€ä¸ªå®ˆæŠ¤è¿›ç¨‹ï¼Œå¯ä»¥å¯åŠ¨åŽå¾ˆå®¹æ˜“çš„åœæ¢å®ƒã€‚ .. code-block:: python #!/usr/bin/env python3 # daemon.py import os import sys import atexit import signal def daemonize(pidfile, *, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): if os.path.exists(pidfile): raise RuntimeError('Already running') # First fork (detaches from parent) try: if os.fork() > 0: raise SystemExit(0) # Parent exit except OSError as e: raise RuntimeError('fork #1 failed.') os.chdir('/') os.umask(0) os.setsid() # Second fork (relinquish session leadership) try: if os.fork() > 0: raise SystemExit(0) except OSError as e: raise RuntimeError('fork #2 failed.') # Flush I/O buffers sys.stdout.flush() sys.stderr.flush() # Replace file descriptors for stdin, stdout, and stderr with open(stdin, 'rb', 0) as f: os.dup2(f.fileno(), sys.stdin.fileno()) with open(stdout, 'ab', 0) as f: os.dup2(f.fileno(), sys.stdout.fileno()) with open(stderr, 'ab', 0) as f: os.dup2(f.fileno(), sys.stderr.fileno()) # Write the PID file with open(pidfile,'w') as f: print(os.getpid(),file=f) # Arrange to have the PID file removed on exit/signal atexit.register(lambda: os.remove(pidfile)) # Signal handler for termination (required) def sigterm_handler(signo, frame): raise SystemExit(1) signal.signal(signal.SIGTERM, sigterm_handler) def main(): import time sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid())) while True: sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime())) time.sleep(10) if __name__ == '__main__': PIDFILE = '/tmp/daemon.pid' if len(sys.argv) != 2: print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr) raise SystemExit(1) if sys.argv[1] == 'start': try: daemonize(PIDFILE, stdout='/tmp/daemon.log', stderr='/tmp/dameon.log') except RuntimeError as e: print(e, file=sys.stderr) raise SystemExit(1) main() elif sys.argv[1] == 'stop': if os.path.exists(PIDFILE): with open(PIDFILE) as f: os.kill(int(f.read()), signal.SIGTERM) else: print('Not running', file=sys.stderr) raise SystemExit(1) else: print('Unknown command {!r}'.format(sys.argv[1]), file=sys.stderr) raise SystemExit(1) è¦å¯åŠ¨è¿™ä¸ªå®ˆæŠ¤è¿›ç¨‹ï¼Œç”¨æˆ·éœ€è¦ä½¿ç”¨å¦‚下的命令: :: bash % daemon.py start bash % cat /tmp/daemon.pid 2882 bash % tail -f /tmp/daemon.log Daemon started with pid 2882 Daemon Alive! Fri Oct 12 13:45:37 2012 Daemon Alive! Fri Oct 12 13:45:47 2012 ... 守护进程å¯ä»¥å®Œå…¨åœ¨åŽå°è¿è¡Œï¼Œå› æ¤è¿™ä¸ªå‘½ä»¤ä¼šç«‹å³è¿”回。 ä¸è¿‡ï¼Œä½ å¯ä»¥åƒä¸Šé¢é‚£æ ·æŸ¥çœ‹ä¸Žå®ƒç›¸å…³çš„pid文件和日志。è¦åœæ¢è¿™ä¸ªå®ˆæŠ¤è¿›ç¨‹ï¼Œä½¿ç”¨ï¼š :: bash % daemon.py stop bash % ---------- 讨论 ---------- 本节定义了一个函数 ``daemonize()`` ,在程åºå¯åŠ¨æ—¶è¢«è°ƒç”¨ä½¿å¾—程åºä»¥ä¸€ä¸ªå®ˆæŠ¤è¿›ç¨‹æ¥è¿è¡Œã€‚ ``daemonize()`` 函数åªæŽ¥å—关键å—å‚æ•°ï¼Œè¿™æ ·çš„è¯å¯é€‰å‚数在被使用时就更清晰了。 它会强制用户åƒä¸‹é¢è¿™æ ·ä½¿ç”¨å®ƒï¼š :: daemonize('daemon.pid', stdin='/dev/null, stdout='/tmp/daemon.log', stderr='/tmp/daemon.log') 而ä¸æ˜¯åƒä¸‹é¢è¿™æ ·å«ç³Šä¸æ¸…的调用: :: # Illegal. Must use keyword arguments daemonize('daemon.pid', '/dev/null', '/tmp/daemon.log','/tmp/daemon.log') 创建一个守护进程的æ¥éª¤çœ‹ä¸ŠåŽ»ä¸æ˜¯å¾ˆæ˜“懂,但是大体æ€æƒ³æ˜¯è¿™æ ·çš„, 首先,一个守护进程必须è¦ä»Žçˆ¶è¿›ç¨‹ä¸è„±ç¦»ã€‚ 这是由 ``os.fork()`` æ“作æ¥å®Œæˆçš„,å进程创建之åŽï¼Œçˆ¶è¿›ç¨‹ç«‹å³è¢«ç»ˆæ¢ã€‚ 在å进程å˜æˆå¤å„¿åŽï¼Œè°ƒç”¨ ``os.setsid()`` 创建了一个全新的进程会è¯ï¼Œå¹¶è®¾ç½®å进程为首领。 它会设置这个å进程为新的进程组的首领,并确ä¿ä¸ä¼šå†æœ‰æŽ§åˆ¶ç»ˆç«¯ã€‚ 如果这些å¬ä¸ŠåŽ»å¤ªé”å¹»ï¼Œå› ä¸ºå®ƒéœ€è¦å°†å®ˆæŠ¤è¿›ç¨‹åŒç»ˆç«¯åˆ†ç¦»å¼€å¹¶ç¡®ä¿ä¿¡å·æœºåˆ¶å¯¹å®ƒä¸èµ·ä½œç”¨ã€‚ 调用 ``os.chdir()`` å’Œ ``os.umask(0)`` 改å˜äº†å½“å‰å·¥ä½œç›®å½•å¹¶é‡ç½®æ–‡ä»¶æƒé™æŽ©ç 。 修改目录通常是个好主æ„ï¼Œå› ä¸ºè¿™æ ·å¯ä»¥ä½¿å¾—它ä¸å†å·¥ä½œåœ¨è¢«å¯åŠ¨æ—¶çš„目录。 å¦å¤–一个调用 ``os.fork()`` åœ¨è¿™é‡Œæ›´åŠ ç¥žç§˜ç‚¹ã€‚ 这一æ¥ä½¿å¾—守护进程失去了获å–æ–°çš„æŽ§åˆ¶ç»ˆç«¯çš„èƒ½åŠ›å¹¶ä¸”è®©å®ƒæ›´åŠ ç‹¬ç«‹ (本质上,该daemon放弃了它的会è¯é¦–领地ä½ï¼Œå› æ¤å†ä¹Ÿæ²¡æœ‰æƒé™åŽ»æ‰“开控制终端了)。 å°½ç®¡ä½ å¯ä»¥å¿½ç•¥è¿™ä¸€æ¥ï¼Œä½†æ˜¯æœ€å¥½ä¸è¦è¿™ä¹ˆåšã€‚ 一旦守护进程被æ£ç¡®çš„分离,它会é‡æ–°åˆå§‹åŒ–æ ‡å‡†I/OæµæŒ‡å‘用户指定的文件。 è¿™ä¸€éƒ¨åˆ†æœ‰ç‚¹éš¾æ‡‚ã€‚è·Ÿæ ‡å‡†I/Oæµç›¸å…³çš„文件对象的引用在解释器ä¸å¤šä¸ªåœ°æ–¹è¢«æ‰¾åˆ° (sys.stdout, sys.__stdout__ç‰ï¼‰ã€‚ 仅仅简å•çš„å…³é— ``sys.stdout`` 并é‡æ–°æŒ‡å®šå®ƒæ˜¯è¡Œä¸é€šçš„, å› ä¸ºæ²¡åŠžæ³•çŸ¥é“它是å¦å…¨éƒ¨éƒ½æ˜¯ç”¨çš„是 ``sys.stdout`` 。 这里,我们打开了一个å•ç‹¬çš„文件对象,并调用 ``os.dup2()`` , 用它æ¥ä»£æ›¿è¢« ``sys.stdout`` 使用的文件æ述符。 è¿™æ ·ï¼Œ``sys.stdout`` 使用的原始文件会被关é—并由新的æ¥æ›¿æ¢ã€‚ 还è¦å¼ºè°ƒçš„是任何用于文件编ç 或文本处ç†çš„æ ‡å‡†I/Oæµè¿˜ä¼šä¿ç•™åŽŸçŠ¶ã€‚ 守护进程的一个通常实践是在一个文件ä¸å†™å…¥è¿›ç¨‹ID,å¯ä»¥è¢«å…¶ä»–程åºåŽé¢ä½¿ç”¨åˆ°ã€‚ ``daemonize()`` 函数的最åŽéƒ¨åˆ†å†™äº†è¿™ä¸ªæ–‡ä»¶ï¼Œä½†æ˜¯åœ¨ç¨‹åºç»ˆæ¢æ—¶åˆ 除了它。 ``atexit.register()`` 函数注册了一个函数在Python解释器终æ¢æ—¶æ‰§è¡Œã€‚ 一个对于SIGTERMçš„ä¿¡å·å¤„ç†å™¨çš„定义åŒæ ·éœ€è¦è¢«ä¼˜é›…çš„å…³é—。 ä¿¡å·å¤„ç†å™¨ç®€å•çš„抛出了 ``SystemExit()`` 异常。 或许这一æ¥çœ‹ä¸ŠåŽ»æ²¡å¿…è¦ï¼Œä½†æ˜¯æ²¡æœ‰å®ƒï¼Œ 终æ¢ä¿¡å·ä¼šä½¿å¾—ä¸æ‰§è¡Œ ``atexit.register()`` 注册的清ç†æ“作的时候就æ€æŽ‰äº†è§£é‡Šå™¨ã€‚ 一个æ€æŽ‰è¿›ç¨‹çš„例å代ç å¯ä»¥åœ¨ç¨‹åºæœ€åŽçš„ ``stop`` 命令的æ“作ä¸çœ‹åˆ°ã€‚ 更多关于编写守护进程的信æ¯å¯ä»¥æŸ¥çœ‹ã€ŠUNIX 环境高级编程》, 第二版 by W. Richard Stevens and Stephen A. Rago (Addison-Wesley, 2005)。 尽管它是关注与Cè¯è¨€ç¼–程,但是所有的内容都适用于Python, å› ä¸ºæ‰€æœ‰éœ€è¦çš„POSIX函数都å¯ä»¥åœ¨æ ‡å‡†åº“ä¸æ‰¾åˆ°ã€‚