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
之前随时通过为实例上的属性赋值来更改。也就是说,对于名为 wibble
和 wubble
的选项,以下调用
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()
方法返回)或 Pythonfile
对象。每个都指定一个在守护进程启动期间不应关闭的文件。chroot_directory
- 默认:
None
要设置为进程的有效根目录的目录的完整路径。如果为
None
,则指定不更改根目录。working_directory
- 默认:
'/'
守护进程启动时应更改到的工作目录的完整路径。
由于如果进程在其当前工作目录位于该文件系统上,则无法卸载文件系统,因此应将其保留为默认值或设置为一个合理的“主目录”,以便守护进程在运行时使用。
umask
- 默认:
0
在守护进程启动时为进程设置的文件访问创建掩码(“umask”)。
由于进程从其父进程继承其 umask,因此启动守护进程会将 umask 重置为此值,以便守护进程创建的文件具有其期望的访问模式。
pidfile
- 默认:
None
PID 锁文件的上下文管理器。当守护进程上下文打开和关闭时,它会进入和退出
pidfile
上下文管理器。detach_process
- 默认:
None
如果为
True
,则在打开守护进程上下文时分离进程上下文;如果为False
,则不分离。如果在实例初始化期间未指定(
None
),则默认情况下将设置为True
,并且仅在确定分离进程是多余的情况下设置为False
;例如,在进程由init
、initd
或inetd
启动的情况下。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
每个
stdin
、stdout
和stderr
都是一个类文件对象,它将用作标准 I/O 流sys.stdin
、sys.stdout
和sys.stderr
的新文件。因此,该文件应处于打开状态,在stdin
的情况下至少为“r”模式,在stdout
和stderr
的情况下至少为“w+”模式。如果对象具有返回文件描述符的
fileno()
方法,则相应的文件将被排除在守护进程启动期间关闭之外(也就是说,它将被视为在files_preserve
中列出)。如果为
None
,则相应的系统流将重新绑定到os.devnull
指定的文件。
定义了以下方法。
open()
- 返回值:
None
打开守护进程上下文,将当前程序转换为守护进程。这将执行以下步骤
- 如果此实例的
is_open
属性为真,则立即返回。这使得在实例上多次调用open
变得安全。 - 如果
prevent_core
属性为真,则设置进程的资源限制以防止进程的任何核心转储。 - 如果
chroot_directory
属性不为None
,则将进程的有效根目录设置为该目录(通过os.chroot
)。这允许在“chroot 监狱”中运行守护进程,作为限制系统暴露于进程的恶意行为的一种手段。请注意,需要提前为该目的设置指定的目录。
- 将进程 UID 和 GID 设置为
uid
和gid
属性值。 - 关闭所有打开的文件描述符。这将排除在
files_preserve
属性中列出的文件描述符,以及与stdin
、stdout
或stderr
属性对应的文件描述符。 - 将当前工作目录更改为
working_directory
属性指定的路径。 - 将文件访问创建掩码重置为
umask
属性指定的值。 - 如果
detach_process
选项为真,则将当前进程分离到其自己的进程组中,并与任何控制终端分离。 - 根据
signal_map
属性设置信号处理程序。 - 如果任何属性
stdin
、stdout
、stderr
不为None
,则将系统流sys.stdin
、sys.stdout
和/或sys.stderr
绑定到相应的属性表示的文件。如果属性具有文件描述符,则将复制该描述符(而不是重新绑定名称)。 - 如果
pidfile
属性不为None
,则进入其上下文管理器。 - 将此实例标记为打开(用于将来的
open
和close
调用)。 - 注册在 Python 的退出处理过程中调用的
close
方法。
当函数返回时,正在运行的程序是一个守护进程。
close()
- 返回值:
None
关闭守护进程上下文。这将执行以下步骤
- 如果此实例的
is_open
属性为假,则立即返回。这使得在实例上多次调用close
变得安全。 - 如果
pidfile
属性不是None
,则退出其上下文管理器。 - 将此实例标记为已关闭(用于将来的
open
和close
调用)。
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)
- 返回值:
- 根据上下文管理器协议定义的
True
或False
调用实例的
close()
方法,然后如果异常已处理则返回True
,否则返回False
。
动机
大多数编写为 Unix 守护进程的程序要么实现了与规范中非常相似的行为,要么是根据正确的守护进程行为表现不佳的守护进程。
由于这些步骤在大多数实现中应该大体相同,但非常特殊且容易遗漏或实现不正确,因此它们是标准库中标准经过良好测试的实现的主要目标。
基本原理
正确的守护进程行为
根据 Stevens 在[stevens] §2.6 中的描述,程序应执行以下步骤以成为 Unix 守护进程。
- 关闭所有打开的文件描述符。
- 更改当前工作目录。
- 重置文件访问创建掩码。
- 在后台运行。
- 与进程组分离。
- 忽略终端 I/O 信号。
- 与控制终端分离。
- 不要重新获取控制终端。
- 正确处理以下情况
- 由 System V
init
进程启动。 - 由
SIGTERM
信号终止守护进程。 - 子进程生成
SIGCLD
信号。
- 由 System V
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 的一些正确的守护进程行为。
该参考实现是以下实现的相当直接的后继者
- 社区为 Python 食谱配方 #66012 [cookbook-66012] 和 #278731 [cookbook-278731]贡献了许多好主意。
bda.daemon
库[bda.daemon]是[cookbook-66012]的实现。它是[python-daemon]的前身。
其他与本 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],实现了 Unixinitd(8)
的等效项,用于控制守护进程。
参考文献
python-daemon
库http://pypi.python.org/pypi/python-daemon/。bda.daemon
库http://pypi.python.org/pypi/bda.daemon/。zdaemon
工具http://pypi.python.org/pypi/zdaemon/。daemon
库http://pypi.python.org/pypi/daemon/。daemonize
库http://daemonize.sourceforge.net/。daemon.py
模块http://www.nightmare.com/~ryb/code/daemon.py。Twisted
应用程序框架http://pypi.python.org/pypi/Twisted/。initd
库http://pypi.python.org/pypi/initd/。版权
此作品特此归入公有领域。在法律上无法将作品归入公有领域的情况下,版权所有者特此授予所有接收此作品的人所有原本受版权限制的权利和自由。
来源:https://github.com/python/peps/blob/main/peps/pep-3143.rst
上次修改时间:2023-09-09 17:39:29 GMT