Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

PEP 3143 – 标准守护进程库

作者:
Ben Finney <ben+python at benfinney.id.au>
状态:
已延期
类型:
标准跟踪
创建:
2009年1月26日
Python 版本:
3.x
发布历史:


目录

摘要

编写一个程序使其成为一个行为良好的 Unix 守护进程在某种程度上是复杂且难以正确实现的,但无论程序还需要做什么,这些步骤在很大程度上对于任何守护进程都是类似的。

本 PEP 将一个包引入 Python 标准库,该包提供了一个简单的接口来实现成为守护进程的任务。

PEP 延期

由于缺乏目前有兴趣推动 PEP 目标、收集和整合反馈以及有足够可用时间有效地做到这一点的维护者,因此对本 PEP 中涵盖的概念的进一步探索已被延期。

规范

使用示例

直接使用 DaemonContext 的简单示例

import daemon

from spam import do_main_program

with daemon.DaemonContext():
    do_main_program()

更复杂的示例用法

import os
import grp
import signal
import daemon
import lockfile

from spam import (
    initial_program_setup,
    do_main_program,
    program_cleanup,
    reload_program_config,
    )

context = daemon.DaemonContext(
    working_directory='/var/lib/foo',
    umask=0o002,
    pidfile=lockfile.FileLock('/var/run/spam.pid'),
    )

context.signal_map = {
    signal.SIGTERM: program_cleanup,
    signal.SIGHUP: 'terminate',
    signal.SIGUSR1: reload_program_config,
    }

mail_gid = grp.getgrnam('mail').gr_gid
context.gid = mail_gid

important_file = open('spam.data', 'w')
interesting_file = open('eggs.data', 'w')
context.files_preserve = [important_file, interesting_file]

initial_program_setup()

with context:
    do_main_program()

接口

一个新的包 daemon 被添加到标准库中。

一个类 DaemonContext 被定义来表示作为守护进程运行的程序的设置和进程上下文。

DaemonContext 对象

一个 DaemonContext 实例代表程序在成为守护进程时的行为设置和进程上下文。通过在调用 open 方法之前设置实例上的选项来自定义行为和环境。

每个选项都可以作为关键字参数传递给 DaemonContext 构造函数,或者随后在调用 open 之前随时通过为实例上的属性赋值来更改。也就是说,对于名为 wibblewubble 的选项,以下调用

foo = daemon.DaemonContext(wibble=bar, wubble=baz)
foo.open()

等价于

foo = daemon.DaemonContext()
foo.wibble = bar
foo.wubble = baz
foo.open()

定义了以下选项。

files_preserve
默认:
None

应在启动守护进程时关闭的文件列表。如果为 None,则将关闭所有打开的文件描述符。

列表的元素是文件描述符(由文件对象的 fileno() 方法返回)或 Python file 对象。每个都指定一个在守护进程启动期间不应关闭的文件。

chroot_directory
默认:
None

要设置为进程的有效根目录的目录的完整路径。如果为 None,则指定不更改根目录。

working_directory
默认:
'/'

守护进程启动时应更改到的工作目录的完整路径。

由于如果进程在其当前工作目录位于该文件系统上,则无法卸载文件系统,因此应将其保留为默认值或设置为一个合理的“主目录”,以便守护进程在运行时使用。

umask
默认:
0

在守护进程启动时为进程设置的文件访问创建掩码(“umask”)。

由于进程从其父进程继承其 umask,因此启动守护进程会将 umask 重置为此值,以便守护进程创建的文件具有其期望的访问模式。

pidfile
默认:
None

PID 锁文件的上下文管理器。当守护进程上下文打开和关闭时,它会进入和退出 pidfile 上下文管理器。

detach_process
默认:
None

如果为 True,则在打开守护进程上下文时分离进程上下文;如果为 False,则不分离。

如果在实例初始化期间未指定(None),则默认情况下将设置为 True,并且仅在确定分离进程是多余的情况下设置为 False;例如,在进程由 initinitdinetd 启动的情况下。

signal_map
默认:
系统相关

操作系统信号到回调操作的映射。

当守护进程上下文打开时使用此映射,并确定每个信号的信号处理程序的操作

  • 值为 None 将忽略信号(通过将信号操作设置为 signal.SIG_IGN)。
  • 字符串值将用作 DaemonContext 实例上属性的名称。属性的值将用作信号处理程序的操作。
  • 任何其他值都将用作信号处理程序的操作。

默认值取决于正在运行的系统上定义了哪些信号。下面列表中的每个项目,其信号实际上在 signal 模块中定义,都将出现在默认映射中

  • signal.SIGTTIN: None
  • signal.SIGTTOU: None
  • signal.SIGTSTP: None
  • signal.SIGTERM: 'terminate'

根据程序如何与其子进程交互,它可能需要指定一个信号映射,其中包括 signal.SIGCHLD 信号(当子进程退出时接收)。有关如何确定需要信号处理程序的具体情况的更多详细信息,请参阅特定操作系统的文档。

uid
默认:
os.getuid()
gid
默认:
os.getgid()

在守护进程启动时切换到的用户 ID(“UID”)值和组 ID(“GID”)值。

默认值(进程的真实 UID 和 GID)将放弃进程继承的任何有效权限提升。

prevent_core
默认:
True

如果为真,则防止生成核心文件,以避免泄露以 root 身份运行的守护进程的敏感信息。

stdin
默认:
None
stdout
默认:
None
stderr
默认:
None

每个 stdinstdoutstderr 都是一个类文件对象,它将用作标准 I/O 流 sys.stdinsys.stdoutsys.stderr 的新文件。因此,该文件应处于打开状态,在 stdin 的情况下至少为“r”模式,在 stdoutstderr 的情况下至少为“w+”模式。

如果对象具有返回文件描述符的 fileno() 方法,则相应的文件将被排除在守护进程启动期间关闭之外(也就是说,它将被视为在 files_preserve 中列出)。

如果为 None,则相应的系统流将重新绑定到 os.devnull 指定的文件。

定义了以下方法。

open()
返回值:
None

打开守护进程上下文,将当前程序转换为守护进程。这将执行以下步骤

  • 如果此实例的 is_open 属性为真,则立即返回。这使得在实例上多次调用 open 变得安全。
  • 如果 prevent_core 属性为真,则设置进程的资源限制以防止进程的任何核心转储。
  • 如果 chroot_directory 属性不为 None,则将进程的有效根目录设置为该目录(通过 os.chroot)。

    这允许在“chroot 监狱”中运行守护进程,作为限制系统暴露于进程的恶意行为的一种手段。请注意,需要提前为该目的设置指定的目录。

  • 将进程 UID 和 GID 设置为 uidgid 属性值。
  • 关闭所有打开的文件描述符。这将排除在 files_preserve 属性中列出的文件描述符,以及与 stdinstdoutstderr 属性对应的文件描述符。
  • 将当前工作目录更改为 working_directory 属性指定的路径。
  • 将文件访问创建掩码重置为 umask 属性指定的值。
  • 如果 detach_process 选项为真,则将当前进程分离到其自己的进程组中,并与任何控制终端分离。
  • 根据 signal_map 属性设置信号处理程序。
  • 如果任何属性 stdinstdoutstderr 不为 None,则将系统流 sys.stdinsys.stdout 和/或 sys.stderr 绑定到相应的属性表示的文件。如果属性具有文件描述符,则将复制该描述符(而不是重新绑定名称)。
  • 如果 pidfile 属性不为 None,则进入其上下文管理器。
  • 将此实例标记为打开(用于将来的 openclose 调用)。
  • 注册在 Python 的退出处理过程中调用的 close 方法。

当函数返回时,正在运行的程序是一个守护进程。

close()
返回值:
None

关闭守护进程上下文。这将执行以下步骤

  • 如果此实例的 is_open 属性为假,则立即返回。这使得在实例上多次调用 close 变得安全。
  • 如果 pidfile 属性不是 None,则退出其上下文管理器。
  • 将此实例标记为已关闭(用于将来的 openclose 调用)。
is_open
返回值:
如果实例已打开,则为 True,否则为 False

此属性公开指示实例当前是否已打开的状态。如果已调用实例的 open 方法且随后未调用 close 方法,则为 True

terminate(signal_number, stack_frame)
返回值:
None

signal.SIGTERM 信号的信号处理程序。执行以下步骤

  • 引发一个 SystemExit 异常,解释信号。

该类还通过 __enter____exit__ 方法实现了上下文管理器协议。

__enter__()
返回值:
DaemonContext 实例

调用实例的 open() 方法,然后返回实例。

__exit__(exc_type, exc_value, exc_traceback)
返回值:
根据上下文管理器协议定义的 TrueFalse

调用实例的 close() 方法,然后如果异常已处理则返回 True,否则返回 False

动机

大多数编写为 Unix 守护进程的程序要么实现了与规范中非常相似的行为,要么是根据正确的守护进程行为表现不佳的守护进程。

由于这些步骤在大多数实现中应该大体相同,但非常特殊且容易遗漏或实现不正确,因此它们是标准库中标准经过良好测试的实现的主要目标。

基本原理

正确的守护进程行为

根据 Stevens 在[stevens] §2.6 中的描述,程序应执行以下步骤以成为 Unix 守护进程。

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

daemon 工具[slack-daemon](在其功能摘要中)列出了将程序转换为行为良好的 Unix 守护进程时应执行的行为。它与本 PEP 的意图不同,因为它调用一个作为守护进程的单独程序。以下功能适用于在程序已运行后自行启动的守护进程

  • 为守护进程设置正确的进程上下文。
  • initd(8)inetd(8) 启动时表现得合理。
  • 撤销任何 suid 或 sgid 权限,以减少在守护进程错误地安装了特殊权限时存在的安全风险。
  • 防止生成核心文件,以防止泄漏以 root 身份运行的守护进程的敏感信息(可选)。
  • 通过创建和锁定 PID 文件来命名守护进程,以确保在任何给定时间只有一个具有给定名称的守护进程可以执行(可选)。
  • 设置运行守护进程的用户和组(可选,仅限 root)。
  • 创建 chroot 目标(可选,仅限 root)。
  • 捕获守护进程的 stdout 和 stderr 并将其重定向到 syslog(可选)。

守护进程不是服务

本 PEP 仅涉及 Unix 样式的守护进程,对于这些守护进程,上述正确行为是相关的,而不是其他操作系统上的可比行为。

许多系统中存在一个相关的概念,称为“服务”。服务不同于本 PEP 中的模型,因为它不是让当前程序继续作为守护进程运行,而是启动一个附加进程在后台运行,并且当前进程通过一些定义的通道与该附加进程通信。

本 PEP 中的 Unix 样式守护进程模型可用于(除其他事项外)实现服务的后台进程部分;但本 PEP 不涉及设置和管理服务的其他方面。

参考实现

python-daemon[python-daemon]

其他守护进程实现

在本文 PEP 之前,一些现有的第三方 Python 库或工具实现了本 PEP 的一些正确的守护进程行为

参考实现是以下实现的相当直接的后继者

其他与本 PEP 不同的 Python 守护进程实现

  • zdaemon 工具[zdaemon]是为 Zope 项目编写的。与[slack-daemon]一样,它与本规范不同,因为它用于将另一个程序作为守护进程运行。
  • Python 库 daemon [clapper-daemon](根据其主页)不再维护。截至 1.0.1 版,它实现了来自[stevens]的基本步骤。
  • daemonize[seutter-daemonize]也实现了来自[stevens]的基本步骤。
  • Ray Burr 的 daemon.py 模块[burr-daemon]提供了[stevens]过程以及 PID 文件处理和将输出重定向到 syslog。
  • Twisted [twisted]可能不出所料地包含一个与 Twisted 框架其余部分集成的进程守护程序 API 的实现;它与本 PEP 中的 API 存在很大差异。
  • Python initd[dagitses-initd],它使用[clapper-daemon],实现了 Unix initd(8) 的等效项,用于控制守护进程。

参考文献

[stevens] (1, 2, 3, 4)
Unix Network Programming,W. Richard Stevens,1994 Prentice Hall。
[slack-daemon] (1, 2)
“raf” <raf@raf.org> 的(非 Python)“libslack” 守护进程工具实现http://www.libslack.org/daemon/
[python-daemon] (1, 2)
Ben Finney 等人编写的 python-daemonhttp://pypi.python.org/pypi/python-daemon/
[cookbook-66012] (1, 2)
Python 食谱配方 66012,“在 Unix 上派生守护进程”http://code.activestate.com/recipes/66012/
[cookbook-278731]
Python 食谱配方 278731,“以 Python 方式创建守护进程”http://code.activestate.com/recipes/278731/
[bda.daemon]
Robert Niederreiter 等人编写的 bda.daemonhttp://pypi.python.org/pypi/bda.daemon/
[zdaemon]
Guido van Rossum 等人编写的 zdaemon 工具http://pypi.python.org/pypi/zdaemon/
[clapper-daemon] (1, 2)
Brian Clapper 编写的 daemonhttp://pypi.python.org/pypi/daemon/
[seutter-daemonize]
Jerry Seutter 编写的 daemonizehttp://daemonize.sourceforge.net/
[burr-daemon]
Ray Burr 编写的 daemon.py 模块http://www.nightmare.com/~ryb/code/daemon.py
[twisted]
Glyph Lefkowitz 等人编写的 Twisted 应用程序框架http://pypi.python.org/pypi/Twisted/
[dagitses-initd]
Michael Andreas Dagitses 编写的 Python initdhttp://pypi.python.org/pypi/initd/

来源:https://github.com/python/peps/blob/main/peps/pep-3143.rst

上次修改时间:2023-09-09 17:39:29 GMT