正确的守护进程行为(来自 PEP 3143)解释

Posted

技术标签:

【中文标题】正确的守护进程行为(来自 PEP 3143)解释【英文标题】:Correct daemon behaviour (from PEP 3143) explained 【发布时间】:2014-06-24 06:35:49 【问题描述】:

我在 Python 中有一些 [针对我的 RPi] 的任务,其中涉及很多 sleeping:做一些需要一两三秒的事情,然后等待几分钟或几小时。 我想在那个睡眠时间将控制权交还给操作系统(Linux)。为此,我应该守护这些任务。一种方法是使用 Python 的标准守护进程库。

但是守护进程并不那么容易理解。根据PEP 3143 的基本原理段落,表现良好的守护进程应该执行以下操作。

关闭所有打开的文件描述符。 更改当前工作目录。 重置文件访问创建掩码。 在后台运行。 与进程组解除关联。 忽略终端 I/O 信号。 与控制终端解除关联。 不要重新获取控制终端。 正确处理以下情况: 由 System V 初始化进程启动。 由 SIGTERM 信号终止守护进程。 儿童生成 SIGCLD 信号。

对于像我这样的 Linux/Unix 新手来说,其中一些很难解释。但我想知道我为什么要做我所做的事情。那么这个理由背后的理由是什么?

【问题讨论】:

相关:New-Style Daemons (systemd, no forking) 【参考方案1】:

PEP 3142 从已故的 W. Richard Stevens 的Unix Network Programming('UNP')中获取了这些要求。下面的解释是从那本书中引用或总结的。它不是那么容易在网上找到的,而且下载可能是非法的。所以我从图书馆借了它。所指页数见第二版,第 1 卷(1998 年)。 (PEP 指的是 1990 年的第一版。)

关闭所有打开的文件描述符。

“我们关闭从执行守护进程(即外壳)的进程继承的所有打开的描述符。[..] 一些守护进程打开/dev/null 用于读取和写入并将描述符复制到标准输入、标准输出和标准错误。”

('Howdy World' Python daemon 演示了这一点。)

“这保证了公共描述符是打开的,并且从这些描述符中的任何一个读取都返回 0(文件结束)并且内核只是丢弃写入这三个描述符中的任何一个的任何内容。打开这些描述符的原因是由守护程序调用的任何库函数,假设它可以从标准输入读取或写入标准输出或标准错误,不会失败。或者,一些守护程序打开一个日志文件,它们将在运行时写入并将其描述符复制到标准输出和标准错误”。 (UNP 第 337 页)

更改当前工作目录

“打印机守护程序可能会更改到打印机的假脱机目录,它在那里完成所有工作。[...] 守护程序可能已在文件系统的任何位置启动,如果它仍然存在,则无法卸载该文件系统。 " (UNP 第 337 页)

为什么要卸载文件系统?两个原因: 1. 您希望从专用于操作系统的目录中分离(并且能够挂载和卸载)可以填充用户数据的目录。 2. 如果您从一个 U 盘启动一个守护进程,您希望能够在不干扰该守护进程的情况下卸载该 U 盘。

重置文件访问创建掩码。

“这样如果守护进程创建自己的文件,继承文件模式创建掩码中的权限位不会影响新文件的权限位。” (UNP,第 337 页)

在后台运行。 根据定义,

“守护进程是一个在后台运行的进程,独立于所有终端的控制”。 (UNP 第 331 页)

与进程组解除关联。 为了理解这一点,您需要了解进程组是什么,这意味着您需要知道fork 的作用。

fork 是做什么的

fork 是(在 Unix 中)创建新进程的唯一方法。 (在 Linux 中,还有clone)。理解fork 的关键是它在被调用(一次)时返回两次:一次在调用进程(=父)中,带有新创建进程(=子)的进程ID,一次在孩子。 “在 fork 时父级知道的所有描述符,在 fork 返回时与子级共享。” (UNP 第 102 页)。 当一个进程想要执行另一个程序时,它会通过调用 fork 创建一个新进程,从而创建一个自身的副本。然后其中一个(通常是孩子)调用新程序。 (UNP,第 102 页)

为什么要脱离进程组

关键是会话领导者可以获取控制终端。守护进程永远不应该这样做,它必须留在后台。这是通过调用fork 两次来实现的:父母分叉创建一个孩子,孩子分叉创建一个孙子。父母和孩子被终止,但孙子仍然存在。但是因为是孙子,不是会话负责人,因此无法获得控制终端。 (摘自 UNP par 12.4 p 335)

在here 和下面的 cmets 中更详细地讨论了双分叉。

忽略终端 I/O 信号。

“从终端密钥生成的信号不得影响之前从该终端启动的任何守护程序”。 (UNP 第 331 页)

与控制终端解除关联,不再重新获取控制终端。 现在,原因很明显了:

"如果守护程序是从终端启动的,我们希望以后能够使用该终端执行其他任务。例如,如果我们从终端启动守护程序,请注销终端,然后其他人在该终端上登录,我们不希望在下一个用户的终端会话期间出现任何守护程序错误消息。” (UNP 第 331 页)

正确处理以下情况:

由 System V 初始化进程启动

很明显,一个守护进程应该可以在启动时启动。

通过 SIGTERM 信号终止守护进程

SIGTERM 表示信号终止。在关闭时,init 进程通常会向所有进程发送 SIGTERM,通常等待 5 到 20 秒,让它们有时间清理和终止。 (UNP,第 135 页)此外,当父母应该停止正在做的事情时,孩子可以向其父母发送 SIGTERM。 (UNP 第 408 页)

儿童产生 SIGCLD 信号

Stevens 讨论的是 SIGCHLD,而不是 SIGCLD。它们之间的区别对于理解守护进程的行为并不重要。如果一个子进程终止,它会向它的父进程发送 SIGCHLD。如果父母没有抓住它,孩子就会变成僵尸(UNP p 118)。哦,多么有趣。

最后一点,当我开始在 UNP 中找到我的问题的答案时,我很快就意识到我真的应该阅读更多内容。这是 900 多页(!),从 1998 年(!)开始,但我相信 UNP 中的概念和解释经得起时间的考验,光荣地。史蒂文斯不仅非常清楚他在说什么,他也明白其中的难处,并使其更容易理解。这真的很少见。

【讨论】:

知道为什么 PEP 被推迟了吗?没关系:python.org/dev/peps/pep-3143/#pep-deferral 真丢脸。 弗兰克,我最终没有使用 python-daemon。我一直在使用这个简单的守护进程:jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python 从您的解释中不清楚为什么孙子不能成为会议负责人(因为一般来说可以)。要了解“与进程组解除关联”部分,请阅读POSIX definition of process groups might help @J.F.Sebastian 您能否从您的链接中详细说明和/或总结一下? 很短的一章:Instapaper 说阅读时间是 22 分钟。

以上是关于正确的守护进程行为(来自 PEP 3143)解释的主要内容,如果未能解决你的问题,请参考以下文章

docker:来自守护进程的错误响应:grpc:连接不可用

守护进程和正常进程之间的行为差​​异是啥?

Linux 守护进程

Python 脚本作为 linux 服务/守护进程

为啥我必须 sudo 来自守护进程的命令?

docker:docker组件