12.14 在Unix系统上面启动守护进程
问题
你想编写一个作为一个在 Unix 或类 Unix 系统上面运行的守护进程运行的程序。
解决方案
创建一个正确的守护进程需要一个精确的系统调用序列以及对于细节的控制。 下面的代码展示了怎样定义一个守护进程,可以启动后很容易的停止它。
要启动这个守护进程,用户需要使用如下的命令:
守护进程可以完全在后台运行,因此这个命令会立即返回。 不过,你可以像上面那样查看与它相关的 pid 文件和日志。要停止这个守护进程,使用:
讨论
本节定义了一个函数 daemonize()
,在程序启动时被调用使得程序以一个守护进程来运行。 daemonize()
函数只接受关键字参数,这样的话可选参数在被使用时就更清晰了。 它会强制用户像下面这样使用它:
创建一个守护进程的步骤看上去不是很易懂,但是大体思想是这样的, 首先,一个守护进程必须要从父进程中脱离。 这是由 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 环境高级编程》。尽管它是关注于 C 语言编程,但是所有的内容都适用于 Python, 因为所有需要的 POSIX 函数都可以在标准库中找到。
Last updated
Was this helpful?