我想编写自己的systemd
单元文件来管理真正长时间运行的命令1(以小时为单位)。在寻找的同时ArchWiki 关于 systemd 的文章,它对选择启动类型进行了以下说明:
Type=simple
(默认):systemd 认为该服务立即启动。该进程不得分叉。如果需要在此服务上订购其他服务,请勿使用此类型,除非它是套接字激活的。
为什么进程根本不能分叉?它是指以守护进程调用过程的方式进行分叉(父分叉,然后退出),还是任何类型的分叉?
1我不需要 tmux/screen,因为我想要一种更优雅的方式来检查状态并重新启动服务,而不需要求助于tmux send-keys
.
答案1
允许服务调用fork
系统调用。 Systemd 不会阻止它,甚至不会注意到它是否阻止了。这句话特指在守护进程开始时进行 fork 的做法,以将守护进程与其父进程隔离。 “进程不得分叉[并在子进程中运行服务时退出父进程]”。
这手册页更详细地解释了这一点,并且使用的措辞不会导致这种特殊的混乱。
许多用作守护进程的程序都有一种模式(通常是默认模式),当它们启动时,它们将自己与父进程隔离。守护进程启动,调用fork()
,然后父进程退出。子进程调用setsid()
,使其在自己的进程组和会话中运行,并运行服务。目的是,如果从 shell 命令行调用守护进程,即使终端发生某些情况(例如终端关闭),守护进程也不会从内核或 shell 接收任何信号(在这种情况下 shell 会发送 SIGHUP到它知道的所有进程组)。这也导致服务进程被 init 采用,当它退出时会收割它,避免了僵尸如果守护进程是由不适合它的东西启动的wait()
(如果守护进程是由 shell 启动的,则不会发生这种情况)。
当守护进程由 systemd 等监控进程启动时,分叉会适得其反。如果服务崩溃,监视进程应该重新启动服务,因此它需要知道服务是否退出,如果服务不是监视进程的直接子进程,那么这就很困难。监控进程不应该永远消失,并且没有控制终端,因此不必担心不需要的信号或收获。因此,服务进程没有理由不成为监视器的子进程,而且有充分的理由这样做。
答案2
忽略这个 Arch 维基页面。
它在设置方面有相当大的错误Type
。而且这不仅限于它的描述simple
。它所说的forking
也是错误的。
对于这类事情的正确建议已经存在了几十年,比 systemd 本身存在的时间还要长,并且至少可以追溯到 20 世纪 90 年代初。正如我在https://unix.stackexchange.com/a/476608/5132,在 systemd doco 中有一个 Johnny 最新版本的守护进程建议,它很大程度上重复了 daemontools 用户、IBM、使用 的人们inittab
,以及……嗯……我几十年来一直在说的。 (当我在 2001 年写下这个问题时,这已经是一个经常给出的答案。)
重复:
如果您的程序有某种“守护进程”机制,特别是分叉子进程并退出父进程,把它关掉和不要使用它。感谢 daemontools 等人。在很长一段时间以来这一直是一个要求的地方,许多计划已经增强了以下能力:没有此类机制在过去 20 多年中一直存在,而其他机制则一开始就没有默认为“守护进程”,因此可以在其默认操作模式下使用。
服务管理子系统启动服务进程已经在守护进程上下文中。这些进程不需要“守护进程”。 (事实上,在许多现代操作系统上,认为程序甚至能来自登录会话上下文的“dæmonize”,这就是“dæmonization”的实际含义。)它们已经具有适合守护进程上下文的环境值和打开文件描述符,以及实际上“dæmonization”完成的几件事阻挠服务管理器定期使用守护进程完成的一些常规操作(例如,将其标准输出/错误捕获到日志中)。
更喜欢Type=simple
,提前打开套接字(服务管理打开服务器套接字并将它们作为已打开的文件描述符传递给服务程序),或Type=notify
.
Type=simple
一旦服务进程启动,就将服务视为就绪(以便可以启动/停止在其上订购的服务),并在客户端尝试连接到服务器以延迟服务客户端时,尽早打开套接字。服务,直到服务器真正准备好为止。Type=notify
具有 systemd 和 Linux 所特有的缺点(以及无法从诸如 shell 生成等短期进程中发挥作用的问题systemd-notify
,以及在特权进程中采用将人类可读形式解析为机器可读形式的问题,其中解析器问题有已经发生了过去),但具有提供更精细的控制(从服务程序的角度)的优点,即服务何时实际上被认为已准备好。它还允许对状态输出进行一些定制。
这两种类型的服务程序都可以分叉。它正在分叉然后退出原来的进程那就是问题所在。
(应该指出的是,从 shell 运行程序和从服务管理器运行程序一样,这是一个问题,用户看到程序终止并几乎立即导致另一个 shell 提示符。事实上,就在今天,有人再次问,关于从 shell 运行分叉并退出父级的程序,位于为什么有时当我在终端中运行程序时,它不会在终端中运行?.)
Type=oneshot
在这种特殊情况下,这可能不是您想要的,因为只有当整个服务程序运行完成时,服务才被视为准备就绪。它有它的用途,但听上去它们并不适用于你。
切勿使用Type=forking
.这应该是绝望中的最后手段,因为几乎没有程序实际上讲协议。他们在别的东西,这实际上是不是该协议无法与该协议正确互操作,并且实际上未发出就绪信号。
进一步阅读
- 乔纳森·德博因·波拉德 (2001)。 设计 Unix 守护程序时要避免的错误。常见答案。
- 乔纳森·德博因·波拉德 (2015)。您确实不需要守护进程。真的。。 systemd 恐怖屋。
- 乔纳森·德博因·波拉德 (2015)。Unix 守护进程的就绪协议问题。经常给出的答案。
- https://unix.stackexchange.com/a/401611/5132