之前在网上看过一篇文章,是讲Tomcat进程意外退出的,我看完感觉好奇,自己也测试了下,果然是有这种问题,所以自己也借此总结一下。
先简单说下测试过程,先创建一个web服务启动 test.sh,内容如下:
#!/bin/bash cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
然后启动该脚本,服务起来了,可以正常访问。
tomcat启动之后,当前shell进程并没有退出,而是挂住在tail进程,往终端输出日志内容。这种情况下:
1)、如果我先直接关掉ssh窗口后,Java进程会退出,服务不可用。
2)、而我如果先 用ctrl-c
终止test.sh进程,然后再关闭ssh终端的话,这时Java进程不会退出。服务没有受到影响,仍然可用。
3)、下面我又把最后tail这一行去掉,发现直接关掉ssh终端窗口,Java进程也不会退出,服务不受影响。
#!/bin/bash cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start
4)、最后我再次恢复tail这一行,然后在开头再加上 set -m, 直接关掉ssh窗口Java进程同样也不会退出,服务同样也不受影响。
#!/bin/bash set -m cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
到这里是不是有点晕了,不急,待我慢慢道出原因来。
这是一个有趣的现象,catalina.sh start
方式启动的tomcat会把java进程挂到init
(进程id为1)的父进程下,已经与当前test.sh
进程脱离了父子关系,也与ssh进程没有关系,为什么关闭ssh终端窗口会导致java进程退出?
经过测试,有发现:
a) 用 ctrl-c 终止当前test.sh进程时,系统events进程向 java 和 tail 两个进程发送了SIGINT
信号
b) 关闭ssh终端窗口时,sshd向下游进程发送SIGHUP
, java进程也会收到。
后来通过google后了解到: shell在非交互模式下对后台进程处理SIGINT
信号时设置的是IGNORE
。
交互模式与非交互模式对作业控制(job control)默认方式不同:
I)在交互模式下,因为作业控制的需要,
shell不会对后台进程处理SIGINT
信号设置为忽略。因此这样的话,父进程会把收到的键盘事件比如ctrl-c
之类的SIGINT
传播给进程组中的每个成员(假设后台进程也是父进程组的成员),那么终端随意ctrl-c
就可能导致所有的后台进程退出,显然这样是不合理的。因此,在交互模式下的后台进程会设置一个自己的进程组ID。
II)而非交互模式下,通常是不需要作业控制的,所以作业控制在非交互模式下默认也是关闭的(当然也可以在脚本里通过选项set -m
打开作业控制选项)。不开启作业控制的话,脚本里的后台进程可以通过设置忽略SIGINT
信号来避免父进程对组中成员的传播,因为对它来说这个信号已经没有意义。
在非交互模式下,shell对java进程设置了SIGINT
,SIGQUIT
信号设置了忽略,但并没有对SIGHUP
信号设为忽略,回头看上面说的,直接关闭ssh终端窗口时,sshd向下游进程发送SIGHUP
, java进程也会收到。
再看一下当时的进程层级:
|-sshd(1622)-+-sshd(11681)---sshd(11699)---bash(11700)---test.sh(13285)---tail(13299)
sshd把SIGHUP
传递给bash进程后,bash会把SIGHUP
传递给它的子进程,并且对于其子进程test.sh,bash还会对test.sh的进程组里的成员都传播一遍SIGHUP
。因为java后台进程从父进程catalina.sh(又是从其父进程test.sh)继承的pgid,所以java进程仍属于test.sh进程组里的成员,收到SIGHUP
后退出。
如果我们在test.sh里设置开启作业控制的话,就不会让java进程退出了
#!/bin/bash set -m cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
此时java后台进程继承父进程catalina.sh的pgid,而catalina.sh不再使用test.sh的进程组,而是自己的pid作为pgid,catalina.sh进程在执行完退出后,java进程挂到了init下,java与test.sh进程就完全脱离关系了,bash也不会再向它发送信号。
注: 如果把tail 打印的这一行去掉,为什么Java进程也不会退出,我猜原因应该是,如果没有tail的话, Java进程就不是他们的子进程了,自然怎么操作都不会受影响。