优雅关闭进程时,我应该按什么顺序发送信号?

116
另一个问题这个答案评论中,评论者说:

除非绝对必要,否则不要使用kill -9! SIGKILL无法被捕获,因此被杀死的程序无法运行任何关机例程以删除临时文件。首先尝试HUP(1),然后是INT(2),然后是QUIT(3)

我原则上同意关于SIGKILL的观点,但其余内容对我来说是新闻。考虑到kill发送的默认信号是SIGTERM,我认为它是最常用于优雅关闭任意进程的预期信号。此外,我已经看到SIGHUP用于非终止原因,例如告诉守护进程“重新读取配置文件”。而且我认为SIGINT(通常与Ctrl-C一起使用的相同中断?)并没有得到应该得到的广泛支持,或者终止得相当不优雅。
鉴于SIGKILL是最后的手段-应该以什么顺序发送哪些信号才能尽可能优雅地关闭任意进程?
如果可以,请用支持事实(超出个人偏好或意见)或参考资料来支持您的答案。
注意:我特别关注包括考虑bash / Cygwin在内的最佳实践。
编辑:到目前为止,似乎没有人提到INT或QUIT,并且对HUP的提及有限。是否有理由在有序的进程终止中包括这些内容?

5
如果你必须使用SIGKILL来真正终止一个进程,我会认为这是程序中的一个错误。 - sigjuice
7个回答

153

SIGTERM是告诉应用程序终止的信号。其他信号告诉应用程序与关闭无关的其他事情,但有时可能会产生相同的结果。不要使用这些信号。如果您想关闭应用程序,请告诉它关闭。不要给它误导性的信号。

有些人认为终止进程的明智标准方式是发送一系列信号,例如HUP、INT、TERM和最后的KILL。这是荒谬的。正确的终止信号是SIGTERM,如果SIGTERM不能立即终止进程,正如您可能希望的那样,那是因为应用程序选择处理该信号。这意味着它有很好的理由不立即终止:它有清理工作要做。如果您使用其他信号中断该清理工作,则无法确定它尚未保存到磁盘的内存数据、哪些客户端应用程序被挂起或者您是否在“句子中间”中断它,这实际上是数据损坏。

有关信号的真正含义的更多信息,请参见sigaction(2)。不要将“默认操作”与“描述”混淆,它们不是同一件事。

SIGINT用于向进程发出交互式“键盘中断”的信号。一些程序可能会以特殊方式处理这种情况,以便终端用户使用。

SIGHUP用于通知终端已经消失并且不再查看该进程。仅此而已。一些进程选择在响应中关闭,通常是因为它们的操作没有终端没有意义,有些则选择执行其他操作,例如重新检查配置文件。

SIGKILL用于强制从内核中删除进程。它在某种意义上是特殊的,因为它实际上不是发送给进程的信号,而是直接由内核解释。

不要发送SIGKILL信号。脚本绝对不能发送SIGKILL信号。如果应用程序处理了SIGTERM信号,它可能需要一秒钟、一分钟,甚至一个小时来完成清理工作,具体取决于应用程序在准备结束之前必须完成的工作。对于任何假定应用程序的清理序列已经足够长并且需要在X秒后被捷径或SIGKILL强制终止的逻辑,都是完全错误的

应用程序只有在其清理序列中出现错误时才会需要使用SIGKILL来终止。在这种情况下,您可以打开终端手动发送SIGKILL。除此之外,您想要防止应用程序自我清理的唯一原因是,您希望防止它完成清理操作。

尽管有一半的人盲目地在5秒钟后发送SIGKILL,但这仍然是非常错误的做法。


20
你说得没错,SIGKILL 的误用确实很普遍。但是,在某些情况下,即使是从脚本中也有使用它的时候。许多应用程序捕获 SIGTERM 信号,并在不到一秒或几秒内优雅地退出,如果一个应用程序仍在运行超过30秒,那就是因为它被卡住了。 - dwc
6
尝试让它运行一小时。如果它没有崩溃,则说明它被“卡住”了,需要修复它,或者在未来某个时间点之后使用SIGKILL命令来懒惰地关闭它。请注意,你很可能会破坏一些东西,并且要记住这不是你默认应该做的事情。 - lhunath
4
希望你不介意,我重新排列了段落,以便回答更直接、更清晰地从问题中得出。反对SIGKILL的言论是很好的内容,但只是次要的观点。再次感谢您提供出色而富有教育性的答案。 - system PAUSE
14
“永远不要发送SIGKILL信号。这样做是完全错误的。” 真的吗?即使您的系统由于无限循环而已经崩溃了。祝你好运。-1 - konsolebox
1
我只在程序无响应时才关心发出停止信号,所以我很乐意在其他任何操作之前发送SIGKILL,而且我不在乎它对数据做了什么,因为我无论如何都会重新启动它。不幸的是,有很多错误存在。 - éclairevoyant

29

简短回答: 发送 SIGTERM 信号,等待 30 秒钟后发送 SIGKILL 信号。也就是说,先发送 SIGTERM 信号,等待一点时间(可能因程序而异,您可能更了解自己的系统,但 5 到 30 秒足够了。在关闭计算机时,您可能会看到它自动等待多达 1 分 30 秒。毕竟为什么要赶急?),然后发送 SIGKILL 信号。

合理回答: 只需要发送 SIGTERMSIGINTSIGKILL 信号就足够了。这些信号很可能在发送 SIGKILL 信号之前就已经终止了进程。

长回答: 发送信号:SIGTERMSIGINTSIGQUITSIGABRTSIGKILL

这并不必要,但至少你没有误导进程关于你的信息。所有这些信号都表示你希望进程停止正在进行的工作并退出。

无论您从本说明中选择哪个答案,都要记住这一点!

如果您发送一个意味着其他事情的信号,进程可能会以非常不同的方式处理它(一方面)。另一方面,如果进程没有处理信号,无论您最终发送什么,进程都将退出(当默认操作是终止时,当然)。

因此,您必须像程序员一样思考。您会为 SIGHUP 编写函数处理程序来退出连接到某个程序的程序,还是将其循环以尝试重新连接?这是主要问题!这就是为什么只发送符合你意图的信号很重要。

几乎愚蠢的长回答:

下表包含相关信号和程序未处理它们时的默认操作。

我按照我建议使用的顺序排列了它们(顺便说一句,我建议您使用合理回答,而不是这里的几乎愚蠢的长回答),如果您真的需要尝试它们所有(它会很有趣,可以说表格按照它们可能导致的破坏程度排序,但这并不完全正确)。

带有星号(*)的信号不推荐使用。这些信号的重要之处在于您可能永远不知道它是如何编程的。特别是 SIGUSR!它可能会引发天启(这是程序员可以随意使用的自由信号!)。但是,如果未处理在不太可能被处理为终止的情况下它被处理为终止,则程序将终止。

在表格中,具有默认选项以终止并生成核心转储的信号位于 SIGKILL 之前,就在最后一个位置。

Signal     Value     Action   Comment
----------------------------------------------------------------------
SIGTERM      15       Term    Termination signal
SIGINT        2       Term    Famous CONTROL+C interrupt from keyboard
SIGHUP        1       Term    Disconnected terminal or parent died
SIGPIPE      13       Term    Broken pipe
SIGALRM(*)   14       Term    Timer signal from alarm
SIGUSR2(*)   12       Term    User-defined signal 2
SIGUSR1(*)   10       Term    User-defined signal 1
SIGQUIT       3       Core    CONTRL+\ or quit from keyboard
SIGABRT       6       Core    Abort signal from abort(3)
SIGSEGV      11       Core    Invalid memory reference
SIGILL        4       Core    Illegal Instruction
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal

我建议为这个几乎愚蠢的长答案提供以下信号:SIGTERMSIGINTSIGHUPSIGPIPESIGQUITSIGABRTSIGKILL

最后,这是一个绝对愚蠢又长的回答:

别在家里尝试这个。

SIGTERMSIGINTSIGHUPSIGPIPESIGALRMSIGUSR2SIGUSR1SIGQUITSIGABRTSIGSEGVSIGILLSIGFPE,如果都不管用,就用SIGKILL

应该先尝试SIGUSR2而不是SIGUSR1,因为如果程序不处理信号,我们会更好。如果它只处理它们中的一个,那么它更有可能处理SIGUSR1

顺便说一下,“杀死”:像其他回答所述,向进程发送SIGKILL并不是错误的。好吧,想想当你发送一个"shutdown"命令时会发生什么?它只会尝试SIGTERMSIGKILL。为什么这样做呢?如果这个“关机”命令只使用这两个命令,那么你为什么需要任何其他信号呢?

现在回到长答案,这是一个不错的一行代码:

for SIG in 15 2 3 6 9 ; do echo $SIG ; echo kill -$SIG $PID || break ; sleep 30 ; done

它在信号之间睡眠30秒钟。为什么你需要一个一行指令呢?;)

另外,建议仅使用来自合理答案的信号15 2 9进行尝试。

安全性:当您准备好时,请删除第二个echo。我把它称为我的dry-run onliners。始终使用它进行测试。


脚本killgracefully

实际上,我对这个问题非常感兴趣,所以我决定创建一个小脚本来完成这个任务。请随意在此处下载(克隆)它:

GitHub链接到Killgracefully repository


1
我一直想知道为什么SIGUSRx的默认操作是终止。如果程序发送该信号,那是因为它希望目标执行特定的操作。如果目标在接收到该信号时实际上没有编程执行任何操作,那么当然,它将无法执行发送信号的进程意图的操作,但这如何意味着立即终止进程是可取的呢?这有点像设计一个操作系统,如果它从没有驱动程序的USB设备接收到数据,就立即关闭计算机。 - flarn2006
如果不是管道,为什么会像“几乎愚蠢的长答案”中建议的那样发送SIGPIPE呢? - Bjorn
正如您已经仔细阅读的那样,这并不是推荐的做法,只是为了展示可能的订单列表而已。但再次强调,不建议这样做。 - DrBeco

8
通常,你应该发送默认的kill信号SIGTERM。用这个信号是有原因的。只有当程序在合理的时间内无法关闭时,你才应该使用SIGKILL。但请注意,使用SIGKILL,程序将没有可能清理任何东西,数据可能会受到破坏。
至于SIGHUPHUP代表“挂起”,历史上意味着调制解调器断开连接。它与SIGTERM基本等价。守护进程有时使用SIGHUP重新启动或重新加载配置文件的原因是,守护进程会从任何控制终端分离出来作为守护进程不需要那些控制,因此永远不会收到SIGHUP,所以该信号被视为“释放”供一般用途。并非所有守护程序都使用该信号进行重新加载!SIGHUP的默认操作是终止,许多守护程序都会表现出这种方式!因此,你不能盲目地向守护进程发送SIGHUP并期望它们幸存。 编辑:SIGINT可能不适合终止进程,因为它通常与^C或终端设置相关联,以中断程序。许多程序为了自己的目的而捕获这个信号,所以这种情况非常普遍。SIGQUIT通常具有创建核心转储的默认值,除非你想让核心文件留下来,否则它也不是一个好选择。
总结:如果你发送SIGTERM并且程序在你设定的时间内没有死亡,则发送SIGKILL

4
请注意,在只有在立即关闭比防止数据丢失/数据损坏更重要的情况下,才应跟随SIGKILL进行关闭。 - thomasrutter
@dwc,我不理解你回答中的以下一点,请帮忙解释一下:“守护进程有时使用SIGHUP来重新启动或重新加载配置的原因是,守护进程会分离任何控制终端,因此永远不会接收到SIGTERM信号,所以该信号被视为“空闲”可供通用使用。” - Jack
3
让我来试试:SIGHUP 是“挂起”信号,告诉进程终端已经断开连接。由于守护进程在后台运行,它们不需要终端。这意味着“挂起”信号对守护进程没有影响。它们永远不会收到来自终端断开连接的信号,因为它们首先没有连接终端。尽管它们不需要原始目的的信号,但由于该信号仍被定义,因此许多守护进程将其用于其他目的,例如重新读取其配置文件。 - system PAUSE
谢谢。PAUSE命令很有用。 - Jack
关于“SIGINT可能不合适”的问题,我有一个进程(不是我的,闭源等),它始终不会响应SIGTERM,但会响应SIGINT。我选择在我的脚本中使用SIGINT,因为它似乎比诉诸于SIGKILL更安全。这样做有问题吗? - Wowfunhappy
1
@Wowfunhappy 这些信号仅仅是约定俗成的,如果它对 SIGTERM 无响应,那么你应该选择一个它能够响应的信号。 - éclairevoyant

7

SIGTERM实际上是向应用程序发送一条消息:“请您自行停止运行”。应用程序可以捕获并处理该信号,以便运行清理和关闭代码。

SIGKILL无法被应用程序捕获。应用程序将被操作系统杀死,没有任何机会进行清理。

通常先发送SIGTERM,然后等待一段时间,再发送SIGKILL


我认为在 SIGKILL 之前使用轮询比睡眠更高效。 - Ohad Schneider
@OhadSchneider 这样做是可行的,但需要更多的东西,而不仅仅是简单的bash命令。 - vartec
是的,我猜你需要在进程仍然存活时使用循环,类似于这样:https://dev59.com/c3A75IYBdhLWcg3w6Ndj#15774758。 - Ohad Schneider

4
  • SIGTERM相当于在窗口中点击“X”按钮。
  • 当Linux关闭时,首先使用SIGTERM。

这就是我想知道的。+1。谢谢。 - Luc
8
"SIGTERM等同于在窗口中点击“X”"。不是的,因为任何一个应用程序都可以轻松打开任意数量的(例如文档和工具)窗口,更不用说对话框了,并且它甚至可能不会响应最后一个窗口关闭命令,就像响应退出命令一样(我想不到任何明显的例子,但虽然不明显,但没有理由不能以这种方式进行)。SIGTERM(或应该)相当于优雅地请求应用程序终止,无论在特定应用程序中如何执行 - user

4

在这里进行的所有讨论中,没有提供任何代码。以下是我的看法:

#!/bin/bash

$pid = 1234

echo "Killing process $pid..."
kill $pid

waitAttempts=30 
for i in $(seq 1 $waitAttempts)
do
    echo "Checking if process is alive (attempt #$i / $waitAttempts)..."
    sleep 1

    if ps -p $pid > /dev/null
    then
        echo "Process $pid is still running"
    else
        echo "Process $pid has shut down successfully"
        break
    fi
done

if ps -p $pid > /dev/null
then
    echo "Could not shut down process $pid gracefully - killing it forcibly..."
    kill -SIGKILL $pid
fi

0

HUP 对我来说听起来像垃圾。我会将其发送给守护进程以重新读取其配置。

SIGTERM 可以被拦截;当您的守护进程接收到该信号时,它可能有清理代码要运行。但是对于 SIGKILL,您无法为其提供任何选项。因此,使用 SIGKILL 时,您不会给守护进程的作者任何选择余地。

更多信息请参见维基百科


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