在Linux上如何检测待处理的系统关机?

26

我正在开发一个应用程序,需要检测系统关闭事件。然而,我没有找到任何可靠的方法来接收此类事件通知。

我知道,在关闭时,我的应用程序会接收到 SIGTERM 信号,随后是 SIGKILL 信号。我想知道是否有任何方法可以查询 SIGTERM 是否是“关闭序列”的一部分?

是否有人知道是否可以通过编程方式(C API)进行查询?

据我所知,系统没有提供任何其他方法来查询即将发生的关机。如果有的话,那也可以解决我的问题。我也尝试了 runlevels,但是转换 runlevels 似乎是瞬间的,没有任何先前的警告。


1
有趣的问题。你想停止关机还是只是收到通知? - ereOn
2
我已经放弃了。我决定将任何SIGTERM视为操作系统想要关闭的消息。我的(可笑的??)理由是,SIGTERM的主要目的是礼貌地请求应用程序干净地退出,如果没有人想让应用程序退出,那么有足够特权的人不太可能发出SIGTERM。即使它不是关闭,应用程序也应该听取它。这让我想到另一个问题。在关机序列中,SIGTERM和SIGKILL之间的最短时间是多少?我知道可以使用-t开关进行配置,但是否有最小限制? - 341008
10个回答

11

或许有点晚了。是的,你可以通过调用runlevel命令来确定SIGTERM是否在关机过程中。例如:

#!/bin/bash
trap "runlevel >$HOME/run-level; exit 1" term
read line
echo "Input: $line"

将其保存为例如term.sh并运行它。通过执行killall term.sh,您应该能够在您的主目录中查看和调查run-level文件。通过执行以下任何一个命令:

sudo reboot
sudo halt -p
sudo shutdown -P

比较文件中的差异,然后你就会知道如何做了。


8

无法确定 SIGTERM 是否是关机序列的一部分。要检测关机序列,您可以使用像 ereOn 和 Eric Sepanson 建议的 rc.d 脚本或使用 DBus 等机制。

然而,从设计角度来看,即使 SIGTERM 不是关机的一部分,忽略它也没有意义。SIGTERM 的主要目的是“礼貌地请求”应用程序干净退出,并且不太可能有足够特权的人发出 SIGTERM 如果他/她不想让应用程序退出。


6

这是一种有点“hack”的方法,但如果服务器正在运行systemd,您可以运行

/bin/systemctl list-jobs shutdown.target

......它会报告......

JOB UNIT            TYPE  STATE
755 shutdown.target start waiting     <---- existence means shutting down

1 jobs listed.

...如果服务器正在关闭或重新启动(提示:如果要针对此特定情况查找,可以使用reboot.target)

如果服务器没有被关闭,你会得到没有正在运行的作业。

你需要解析输出内容,因为systemctl不会为这两种结果返回不同的退出代码。但它似乎相当可靠。但是,如果更新系统,消息的格式可能会发生变化,需要注意。


6

来自命令shutdown的man手册:

如果使用时间参数,系统关机前5分钟会创建/etc/nologin文件,以确保不允许更多的登录。

因此,您可以测试/etc/nologin文件是否存在。虽然不是最佳方法,但这可能是您能够获得的最佳方法。


3

我想我明白了。

来源 = https://github.com/mozilla-b2g/busybox/blob/master/miscutils/runlevel.c

为防止参考链接消失,我在此处复制了部分代码。

#include "libbb.h"
...
struct utmp *ut;
char prev;

if (argv[1]) utmpname(argv[1]);

setutent();
while ((ut = getutent()) != NULL) {
    if (ut->ut_type == RUN_LVL) {
        prev = ut->ut_pid / 256;
        if (prev == 0) prev = 'N';
        printf("Runlevel: prev=%c current=%c\n", prev, ut->ut_pid % 256);
        endutent();
        return 0;
    }
}
puts("unknown");

3
让你的应用程序对某些SIGTERM信号作出不同的响应似乎很难理解,可能会引起混淆。有人认为你应该始终以相同的方式响应给定的信号。添加异常条件会使应用程序行为更难理解和测试。
添加一个处理关闭的rc脚本(通过发送特殊信号)是完全标准的处理此类问题的方法;如果将此脚本安装为标准软件包的一部分(如make install或rpm/deb打包),则不必担心用户机器的控制。

3

请查看man systemctl,您可以通过以下方式确定系统是否正在关闭:

if [ "`systemctl is-system-running`" = "stopping" ]; then
    # Do what you need
fi

这是Bash中的代码,但您也可以使用C语言中的“system”函数来实现。

1
但如果任何单元受损,我认为它可能会显示为“受损”,而不是停止。 - Jack Wasey
2
实际上,您还可以查看此命令的退出值;如果系统处于正常的“运行”状态,则以0状态退出,如果系统处于任何其他状态,则将返回> 0的退出值。 您可以在systemctl手册页面上获取所有可能的输出:https://www.freedesktop.org/software/systemd/man/systemctl.html - 我已经使用作为systemd服务单元运行的Scala应用程序测试了此方法,并且它有效。 - 2072

2
原本想要的实际答案是检查关机进程(例如 ps aux | grep "shutdown -h" ),然后如果想要确定,可以检查其命令行参数和启动时间(例如“shutdown -h +240”在14:51启动,将在18:51关机)。
从整个系统的角度来看,在一般情况下无法做到这一点。关机可能有许多不同的方式。例如,某人可以决定拔掉插头以硬停止他们现在知道在关机时表现糟糕/危险的程序,或者UPS可能首先发送SIGHUP,然后简单地失败。由于这种关机可能突然发生并且没有任何警告,无论在系统的任何地方,因此无法确定在SIGHUP之后继续运行是否安全。
如果一个进程接收到SIGHUP信号,你应该基本上假设很快就会发生更糟糕的事情。如果你想要做一些特殊的事情并部分忽略SIGHUP,那么a) 你需要与将执行关机的任何程序协调,并且b) 你需要准备好,如果其他系统在SIGHUP之后立即关闭并杀死你,你的软件和数据将幸存下来。将你拥有的任何数据写出,只继续写入具有安全原子更新的追加文件。
对于你的情况,我几乎确定你当前的解决方案(将所有SIGHUP视为关机)是正确的方法。如果你想要改进,你可能应该添加一个通过DBUS或类似的东西进行通知的关机程序功能。

1

当系统关闭时,将调用rc.d脚本。

也许您可以在那里添加一个脚本,向您的程序发送一些特殊信号。

但是,我怀疑您无法通过这种方式停止系统关闭。


感谢您的快速回复。我无法控制我的应用程序将部署在哪些机器上,因此我无法对它们进行任何更改。此外,我不想停止关机。我只是想知道何时将发生关机。 - 341008
很抱歉,我想不出还有什么其他的事情可以做了。也许如果您解释一下您想要实现什么,我们可以帮助您找到替代方案? - ereOn

0
请参阅“man shutdown”。请注意,如果要指定墙消息,则还必须指定时间参数。如果使用时间参数,在系统关闭5分钟之前,将创建“/run/nologin”文件以确保不允许进一步登录。
Python示例:
if os.path.exists("/run/nologin"):
  print(now.strftime('Shutdown is pending yet %H:%M:%S on %A, %B the %dth, %Y'))
else:
  print(now.strftime('Waiting for shutdown-press at %H:%M:%S on %A, %B the %dth, %Y'))
  os.system("sudo shutdown -h +2 'Power will turn off in 2 min. Please Log Out.'")

目前你的回答不够清晰,请[编辑]以添加更多细节,帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接