Linux 中不同退出信号的区别
后知后觉 暂无评论

相信 kill 命令大家都不陌生,程序卡住需要强制退出的场景应该是都遇到过,那么为什么有的程序直接 kill 就能退出,为什么有的需要 kill -9 才能退出呢?

想要在 Linux 中终止一个进程有两种方式,如果是前台进程(比如脚本)可以使用 Ctrl+C 快捷键键进行终止;如果是后台进程,那么需要使用 kill 命令来终止。

其实快捷键 Ctrl+C 也是发送的 kill 命令,这个后面再说。

kill 命令

命令的使用格式如下:

kill [参数] [PID(进程号)]

那么问题来了,程序如何知道一个命令想要做什么呢?答案很简单,就是发 singal(信号),命令将信号发送给程序,程序就知道了这个命令想完成的操作,比如在默认情况下,执行 kill 命令会发送的信号为 15。

信号是一个预先定义的数字,Linux 中所有的信号种类可以使用命令 kill -l 来查看。

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL      5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

代号前面的数字就是此信号的数字代码,发送数字代码的效果等同于代号。

信号ID作用和说明
HUP1终端断线
INT2中断(同 Ctrl + C)
QUIT3退出(同 Ctrl + \)
KILL9强制终止
TERM15终止
CONT18继续(与STOP相反, fg/bg命令)
STOP19暂停(同 Ctrl + Z)

退出信号间差异

最后单独说一下常见的几个程序退出的信号和它们之间的区别。

ID信号说明
2)SIGINTInterrupt(程序终止)信号,由 INTR 字符(通常是Ctrl-C)控制,一般用于通知前台进程组终止进程。
3)SIGQUITQuit(程序退出)信号,与 SIGINT 信号类似,由 QUIT 字符(通常是Ctrl-\)控制。进程在收到 SIGQUIT 退出时会产生 core dump 文件,一般用来告知程序异常退出。
15)SIGTERMTerminate(程序结束)信号,与 SIGKILL 不同的是该信号可以被程序阻塞或忽略,一般用于要求程序自行安全退出,程序本身可以定义此信号的行为,因此可能延时很长 。
19)SIGSTOPStopped(程序停止)信号,这个信号是通知进程暂停执行,并不结束,此信号不能被阻塞,处理或者忽略。

当使用 SIGTERM(15) 时(即:kill -15),系统会发送 SIGTERM(15) 信号给对应的程序。当程序接收到该信号后,具体要如何处理是自己可以决定的,可以将此种方式理解为“优雅”退出。

这时候,应用程序自定义此信号的行为,比如:

程序接到信号之后,退出前一般会进行一些“准备工作”,如资源释放、临时文件清理等等,如果准备工作做完了,再进行程序的终止。
但是,如果在"准备工作"进行过程中,遇到阻塞或者其他问题导致无法成功,那么应用程序可以选择忽略该终止信号。

而当使用 SIGKILL(9) 时,系统会强硬结束程序,会在系统层面直接要求程序立即结束,不能被阻塞和忽略。

这也就是为什么有的时候单纯使用 kill 命令是没办法"杀死"应用的原因。并且在执行 kill -9 时,应用程序是没有时间进行"准备工作"的,所以这通常会带来一些副作用,比如数据丢失、状态丢失等等。

额外补充一下,比如在容器中,因为程序的不同,所以需要使用 Dockerfile 的 STOPSIGNAL 参数来定义容器镜像的退出方式,防止出现程序卡死无法正常退出容器的问题。

Java 中的退出信号

在 Linux 中,Java 应用是作为一个独立进程运行的,Java 程序的终止运行是基于 JVM 的关闭实现的,JVM 关闭方式分为3种:

JVM 进程在接收到 SIGTERM 信号通知的时候,是可以做一些清理动作的,比如删除临时文件等。当然,开发者也是可以自定义做一些额外的事情的,比如让容器停止,让 dubbo 服务下线等。而这种自定义退出动作的方式,是通过 JDK 中提供的 shutdown hook 实现的。

JDK 提供了 Java.Runtime.addShutdownHook(Thread hook) 方法,可以注册一个 JVM 关闭的钩子。例子如下:

package com.hollis;

public class ShutdownHookTest {


    public static void main(String[] args) {

        boolean flag = true;

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {

            System.out.println("hook execute...");

        }));


        while (flag) {

            // app is runing

        }


        System.out.println("main thread execute end...");

    }

}

然后执行命令 kill -15

➜ jps

6520 ShutdownHookTest

6521 Jps

➜ kill 6520

控制台输出内容:

hook execute...

Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

可以看到,当使用默认 kill 信号关闭进程的时候,程序会先执行用户定义的 shutdownHook 方法后再退出。

如果执行命令 kill -9

➜ kill -9 6520

控制台输出内容:

Process finished with exit code 137 (interrupted by signal 9: SIGKILL)

可以看到,当使用 kill -9 强制关闭进程的时候,程序并没有执行 shutdownHook,而是直接退出。


附录

参考链接

本文撰写于一年前,如出现图片失效或有任何问题,请在下方留言。博主看到后将及时修正,谢谢!
禁用 / 当前已拒绝评论,仅可查看「历史评论」。