9

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

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

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

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

4

1 回答 1

13

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

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

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

(这个'Howdy World' Python 守护进程演示了这一点。)

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

更改当前工作目录

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

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

重置文件访问创建掩码。

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

在后台运行。
根据定义,

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

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

叉子做什么

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

为什么要脱离进程组

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

双叉在此处和下面的评论中进行 了更详细的讨论。

忽略终端 I/O 信号。

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

与控制终端解除关联,不要重新获取控制终端。
到目前为止,原因很明显:

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

正确处理下列情况:

  • 由 System V init 进程启动

    • 很明显,一个守护进程应该可以在启动时启动。
  • SIGTERM 信号终止守护进程

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

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

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

于 2014-05-07T10:23:58.607 回答