为什么 systemd 中的简单类型服务“进程不能分叉”?

che*_*ase 13 fork systemd

我想编写自己的systemd单元文件来管理真正长时间运行的命令1(以小时为单位)。在查看关于 systemdArchWiki 文章时,它说明了以下关于选择启动类型的内容:

Type=simple(默认):systemd 认为服务会立即启动。进程不能 fork。如果需要在此服务上订购其他服务,请不要使用此类型,除非它是套接字激活的。

为什么进程根本不能分叉?它是指以守护进程召唤过程(父分叉,然后退出)的风格进行分叉,还是任何类型的分叉?


1我不想要 tmux/screen,因为我想要一种更优雅的方式来检查状态和重新启动服务,而无需求助于tmux send-keys.

Gil*_*il' 43

允许服务调用fork系统调用。Systemd 不会阻止它,甚至不会注意到它。这句话特指在守护进程开始时分叉以将守护进程与其父进程隔离的做法。“进程不得 fork [并在子进程中运行服务时退出父进程]”。

手册页解释了这个要冗长,并用措辞不会导致这种特殊的混乱。

许多打算用作守护进程的程序都有一个模式(通常是默认模式),当它们启动时,它们将自己与父进程隔离开来。守护进程启动,调用fork(),父进程退出。子进程调用setsid()以使其在自己的进程组和会话中运行,并运行服务。目的是,如果守护程序是从 shell 命令行调用的,即使终端发生某些事情,例如终端关闭(在这种情况下,shell 会发送 SIGHUP到它知道的所有进程组)。这也导致服务进程被 init 采用,它会在退出时收获它,如果守护进程被一些不会启动的东西启动,则避免僵尸wait() 对于它(如果守护程序是由 shell 启动的,则不会发生这种情况)。

当守护进程由 systemd 等监控进程启动时,分叉会适得其反。如果服务崩溃,监控进程应该重新启动服务,所以它需要知道服务是否退出,如果服务不是监控进程的直接子项,这很困难。监控过程不应该永远消失,也没有控制终端,因此不必担心不需要的信号或收割。因此,服务进程没有理由不成为监视器的子进程,并且有充分的理由让它成为。


Jde*_*eBP 15

忽略这个 Arch 维基页面。

它在Type设置方面有相当大的错误。此外,这不仅限于对 的描述simple。它所说的forking也是错误的。

对这类事情的正确建议已经存在了几十年,比 systemd 本身存在的时间还要长,至少可以追溯到 1990 年代初。正如我在https://unix.stackexchange.com/a/476608/5132所指出的,在 systemd doco 中,有一个 Johnny-come-lately 版本的 dæmons 建议,它在很大程度上重复了 daemontools 用户、IBM、使用inittab和......好吧......我已经说了几十年了。(当我在 2001 年这样写时,这已经是一个经常给出的答案。)

重复:

如果你的程序有一些“系统守护进程”机制,即尤其叉一个孩子并退出父进程,关掉它没有使用它。感谢 daemontools 等人。长期以来,这一直是一个要求,在过去的 20 多年里,许多程序已经发展出没有这种机制的能力,而其他程序根本就没有默认为“dæmonizing”,因此可以用于它们的默认操作模式。

服务管理子系统已经在守护进程上下文中启动服务进程。这些进程不需要“dæmonize”。(事实上​​,许多现代操作系统认为程序甚至可以从登录会话上下文“dæmonization”是一种谬论,这就是“dæmonization”的真正含义。)他们已经有了环境值,并打开文件描述符,适合守护进程上下文,并且“守护进程”所做的几件事实际上阻碍了服务管理器定期使用守护进程完成的一些常规事情(例如,将其标准输出/错误捕获到日志中)。

首选Type=simple,早期套接字打开(服务管理打开服务器套接字并将它们作为已经打开的文件描述符传递给服务程序),或Type=notify.

  • Type=simple 服务进程一开始就将服务视为就绪(以便在其上订购的服务可以启动/停止),早期套接字打开使用套接字连接语义来延迟服务客户端,在它们尝试连接到服务器时服务,直到服务器真正准备就绪。
  • Type=notify具有 systemd 和 Linux 所特有的缺点(以及无法从诸如 shell 生成等短期进程运行的问题systemd-notify,以及在特权进程中使用人类可读形式解析为机器可读形式的问题,其中解析器问题在过去已经发生过)但具有提供更精细的控制(从服务程序的角度来看)服务何时真正被认为准备好的优点。它还允许对状态输出进行一些自定义。

两种类型的服务程序都可以分叉。它正在分叉然后退出原来的进程,这就是问题所在。

(应该注意,这对于从 shell 运行程序和从服务管理器运行程序来说都是一个问题,用户看到程序终止并几乎立即导致另一个 shell 提示。确实,就在今天有人问,又一次,关于从 shell 运行程序,fork 并退出父级,为什么有时当我在终端中运行程序时,它不会在终端中运行?。)

Type=oneshot在这种特殊情况下可能不是您想要的,因为只有当整个服务程序运行完成时,服务才被视为准备就绪。它有它的用途,但从它的声音来看,它们不适用于你。

永远不要使用Type=forking. 这应该是万不得已的最后手段,因为几乎没有程序真正讲协议。他们正在做其他事情,这实际上不是这个协议,不能正确地与这个协议互操作,实际上也不是信号准备就绪。

进一步阅读

  • 由于它是一个 wiki,标题的更好版本可能是:“改进这个 Arch Wiki 页面!” :) (2认同)