12.14 在Unix系统上面启动守护进程

    创建一个正确的守护进程需要一个精确的系统调用序列以及对于细节的控制。下面的代码展示了怎样定义一个守护进程,可以启动后很容易的停止它。

    要启动这个守护进程,用户需要使用如下的命令:

    守护进程可以完全在后台运行,因此这个命令会立即返回。不过,你可以像上面那样查看与它相关的pid文件和日志。要停止这个守护进程,使用:

    而不是像下面这样含糊不清的调用:

    创建一个守护进程的步骤看上去不是很易懂,但是大体思想是这样的,首先,一个守护进程必须要从父进程中脱离。这是由 os.fork() 操作来完成的,并立即被父进程终止。

    在子进程变成孤儿后,调用 os.setsid() 创建了一个全新的进程会话,并设置子进程为首领。它会设置这个子进程为新的进程组的首领,并确保不会再有控制终端。如果这些听上去太魔幻,因为它需要将守护进程同终端分离开并确保信号机制对它不起作用。调用 os.chdir() 和 改变了当前工作目录并重置文件权限掩码。修改目录通常是个好主意,因为这样可以使得它不再工作在被启动时的目录。

    一旦守护进程被正确的分离,它会重新初始化标准I/O流指向用户指定的文件。这一部分有点难懂。跟标准I/O流相关的文件对象的引用在解释器中多个地方被找到(sys.stdout, sys.stdout等)。仅仅简单的关闭 sys.stdout 并重新指定它是行不通的,因为没办法知道它是否全部都是用的是 sys.stdout 。这里,我们打开了一个单独的文件对象,并调用 os.dup2() ,用它来代替被 使用的文件描述符。这样,sys.stdout 使用的原始文件会被关闭并由新的来替换。还要强调的是任何用于文件编码或文本处理的标准I/O流还会保留原状。

    守护进程的一个通常实践是在一个文件中写入进程ID,可以被其他程序后面使用到。daemonize() 函数的最后部分写了这个文件,但是在程序终止时删除了它。atexit.register() 函数注册了一个函数在Python解释器终止时执行。一个对于SIGTERM的信号处理器的定义同样需要被优雅的关闭。信号处理器简单的抛出了 SystemExit() 异常。或许这一步看上去没必要,但是没有它,终止信号会使得不执行 注册的清理操作的时候就杀掉了解释器。一个杀掉进程的例子代码可以在程序最后的 stop 命令的操作中看到。

    更多关于编写守护进程的信息可以查看《UNIX 环境高级编程》, 第二版by W. Richard Stevens and Stephen A. Rago (Addison-Wesley, 2005)。尽管它是关注与C语言编程,但是所有的内容都适用于Python,因为所有需要的POSIX函数都可以在标准库中找到。