如果没有 ExecStop 语法,服务守护进程如何正常关闭?

如果没有 ExecStop 语法,服务守护进程如何正常关闭?

我是一名 systemd 初学者。

ExecStop=我想知道如果没有systemd 服务单元文件中的语法,如何正常终止服务。

例如,对于mongod.service,没有ExecStop=

[Unit]
Description=MongoDB Database Server
Documentation=https://docs.mongodb.org/manual
After=network.target

[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
PIDFile=/var/run/mongodb/mongod.pid

# [The rest, irrelevant setrlimit(2)/sh(1) `ulimit' settings, such as...]
LimitFSIZE=... [...are all snipped.]

[Install]
WantedBy=multi-user.target

ExecStop=但是,就我而言,如果我从文件中删除serviceDaemon.service并输入systemctl stop command,它不会正常关闭。

× serviceDaemon.service - server integrity checker
     Loaded: loaded (/etc/systemd/system/serviceDaemon.service; disabled; vendor preset: enabled)
     Active: failed (Result: timeout) since Tue 2023-02-28 19:57:30 KST; 3min 38s ago
    Process: 1235476 ExecStart=/opt/esm/bin/serviceDaemon -D 9 -d (code=killed, signal=KILL)
   Main PID: 1235476 (code=killed, signal=KILL)
        CPU: 395ms

Feb 20 14:36:41 esm-dev systemd[1]: Started server integrity checker.
Feb 28 19:56:00 esm-dev systemd[1]: Stopping server integrity checker...
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: State 'stop-sigterm' timed out. Killing.
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: Killing process 1235476 (intchecker) with signal SIGKILL.
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: Killing process 1235478 (intchecker) with signal SIGKILL.
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: Killing process 1235479 (intchecker) with signal SIGKILL.
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: Main process exited, code=killed, status=9/KILL
Feb 28 19:57:30 esm-dev systemd[1]: serviceDaemon.service: Failed with result 'timeout'.

我想知道为什么像Mongodb这样的命令serviceDaemon不能正常终止服务systemctl stop

答案1

了解系统如何工作的最好方法是以所有可能的方式破坏它
      ——无论是匿名的还是年轻的我自己,我真的不记得了。

所以,你的实验非常好,并且确实具有重要的教学价值。许多人将 systemd 视为一项与魔法无异的技术。这是错误的:它是一个不太一致但有很好记录的系统服务管理器。这很棘手,需要阅读手册。它们非常完整。


你的问题的答案是,这在 systemd 中很常见,这取决于情况。 systemd 是一个复杂而灵活的系统服务管理器。它主要取决于类型的服务,由Type=和 的不存在/存在定义BusName=。如果没有两者,就像你的情况一样,服务是简单的: systemd 只是运行它,假设启动已经成功,即使启动后 10 毫秒就死掉了。它根本无法知道它会发生,而且它的主要的设计目标是尽快使系统在现代 56 核 CPU 上进入工作状态。谁会浪费10毫秒...

有许多不同的协议用于启动服务,因为 systemd 希望尽可能准确地知道服务实际上已成功启动。它们Type=在 systemd.service(5) 手册的设置下进行了描述。其中,仅一次性类型不同:该服务被 systemd 视为“活动”,同时它执行得很快并以退出代码 0 终止。如果您想这样做,这是有意义的某物当一个单元启动时,以及当别的东西它已经停止了,但不是在中间。如果成功,则它处于活动状态,并且在您检查其状态时有一个绿色按钮。当你停止它时,它就会执行它的ExecStop=事情。例如,Linux NFS4 服务器是一个内核设施,其服务 systemd 单元仅在启动时启用,在停止时禁用,需要几纳秒的时间来调用内核并终止。没有任何进程需要在 NFS 服务器提供服务的整个过程中一直运行。它的单元处于活动状态,但没有正在运行的用户空间进程。

对于所有其他类型,缺席ExecStop=,行为发起服务关闭也是一样的:向服务发送一个信号。该信号由KillSignal=, (或用于重新启动)定义RestartKillSignal=,或者,如果未设置,则为标准服务终止信号 SIGTERM (请参阅 systemd.kill(5))。自从猛禽在地球上漫游以来,这就是所有“传统”Unix 守护进程被告知终止的方式。如果服务无法停止,协议也会有所不同:例如,通过 dbus 与 systemd 通信的服务可能会请求更多时间来关闭。如果没有这个,TimeoutStopSec=则使用该设置,或(手册中的 system.conf(5))system.conf 中设置的默认值DefaultTimeoutStopSec=,或编译时默认值,通常为 90 秒,除非系统供应商更改。之后,systemd粗鲁地用SIGKILL杀死进程,这是无法屏蔽或忽略的:进程就这样死掉了,它可以释放的内存被释放,它被认为终止并成为僵尸;然后,一旦所有内核模块对它失去兴趣(例如,如果它在磁盘或 GPU DMA 传输期间被杀死,在 read(3) 内,内核将通知驱动程序模块尽快中断或完成传输),从所有系统表中删除。换句话说,它像任何其他进程一样终止。有一些与(a)其子进程和(b)其控制组相关的微妙之处,但您可以阅读手册以了解这些血淋淋的细节。

回到你自己造成的问题:

  1. 如果没有Type=BusName=,服务是简单的
  2. 缺席ExecStop=,一个简单的向服务发送 SIGTERM 信号,并(可能)给它 90 秒来终止自身及其子进程。
  3. 如果失败,systemd 就会对其进行全面武士攻击,并使用 SIGKILL 无情地杀死它。

你的例子确实是一个很好的例子!让我们逐行解析它。

2 月 28 日 19:56:00 esm-dev systemd[1]:正在停止服务器完整性检查程序...
2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:状态“stop-sigterm”超时。杀戮。

请注意,两条消息之间经过的时间正好是 90 秒。该服务显然并未设计为在收到 SIGTERM 时停止。第二行表明内部 systemd 状态已发送 SIGTERM、正在等待并已超时。 “杀死”意味着发送 SIGKILL 信号,您的进程甚至没有注意到该信号:这是向内核发出的要删除的信号那个东西尽可能快。

2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:使用信号 SIGKILL 杀死进程 1235476 (intchecker)。
2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:使用信号 SIGKILL 杀死进程 1235478 (intchecker)。
2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:使用信号 SIGKILL 杀死进程 1235479 (intchecker)。

systemd 足够聪明,可以处理整个进程的生成。它的所有子进程也同时被无条件杀死(只有主要的PID 与 systemd 对话,子进程不是 systemd 的事,除非行为不当)。

2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:主进程已退出,代码=killed,状态=9/KILL

一旦进程及其在其组领导下启动的所有内容都被 SIGKILL 杀死(9 是 SIGKILL 的数值;kill -SIGKILL <pid>并且kill -KILL <pid>kill -9 <pid>完全相同的命令),无论是否僵尸化(这是内核的问题,而不是 systemd 的问题),systemd在内部将服务设置为失败:

2 月 28 日 19:57:30 esm-dev systemd[1]:serviceDaemon.service:失败,结果为“超时”。

现在允许它继续处理其依赖关系图。

请注意,大屠杀一开始,所有事件的时间戳都为 19:57:30。 systemd现在的问题是尽快摆脱叛逆的进程。它非常擅长,因此您需要在启动服务时尽早为 SIGTERM 建立一个处理程序。更好地合作。


作为服务作者,您有多种选择。 A简单的服务是最快的,但信息量最少:systemd 一旦它分叉了一个内部工作单元,就会将其发布为启动状态,或者工作,启动二进制文件。即使ExecStart=文件丢失,您的服务也会成功启动。它将显示一个红色按钮,以及整体“降级”状态,但不会阻止其他服务的启动。

下一个更确定但更慢的是执行类型:systemd 将确保该进程在发布成功之前已启动。

从此时起,决定是否要通过 dbus 与 systemd 对话。如果您知道系统是由 systemd 管理的,那么这样做是有好处的。您通常会链接到libsystemd.so.1,它具有高级 API,但也可以不使用它 — 它只是一个具有众所周知名称的 UNIX 套接字。我见过一些代码不想依赖这个库,但如果 dbus 存在,则自行与它对话。这种情况非常罕见,但允许由 systemd 或传统 init 启动相同的二进制文件,而无需重新编译。

有了 dbus,下一个级别的确定性是总线类型。 systemd 在与 dbus 建立连接时认为该服务已启动(所以不要犹豫,尽早启动:它会影响整个系统的启动)。

接下来是通知服务。仅当服务发布READY=1到其 dbus 连接后,systemd 才会发布成功。这是对 systemd 来说信息最丰富的,因为它有一个积极确认的服务启动。

两种类型都不需要 fork(2)。

否则,您可以将该服务实现为“传统”自我守护进程。启动后执行所有初始化,然后,如果一切准备就绪,则将进程 fork(2) 为两个克隆。原来的退出并向 systemd 返回成功状态。第二个仍在运行,并且是主要的长时间运行的守护进程。传统上,它们会在文件系统<servicename>.pid下写入一个文件/run,但 systemd 通常会找到没有该文件的分叉进程。如果您保存一个,请通过设置告诉 systemd 它的位置PIDFile=。如果您留下它,systemd 会将其删除。仅“传统”init/rc 系统需要此文件。Type=此类服务的功能是分叉。它还提供正向启动指示到 systemd,就像通知类型。没有其他长时间运行的类型可以做到(一次性然而,它的启动命令也应该尽快完成)。

请注意,此顺序是关于 systemd 的肯定该服务已启动。从启动速度来看,比例几乎颠倒了,而且简单的类型是性能最佳的。因此,如果您知道其他服务不依赖于您的服务已完成初始化,请选择快速。否则,请选择信息最丰富的,分叉或者通知,请记住,这对系统启动时间的影响最大。

相关内容