Linux shell 退出后任务仍然运行引发的探究
今天通过 SSH 连接到服务器的 Shell 时,执行了一个 Python 脚本,并通过CTRL+Z
和bg
将其放到了后台运行。
之后我退出了该 shell,但出乎意料的是,当我再次登录 shell 时该脚本仍然在运行,而正常来说,该脚本应该在 shell 退出时被终止。
于是我查阅了相关文档,探究其原因。
通常情况
当我们登录到交互式的 shell 之后,所执行的命令一般都是派生自当前 shell 进程,作为其子进程运行。
退出 shell 时,所有任务(子进程)都会接收到该 shell 发送的 SIGHUP 信号,从而被终止。
要避免任务在退出 shell 时被终止,一般要通过nohup
、disown
、setsid
等命令来实现。
具体方法可参考该文章:ofollow,noindex" target="_blank">Linux 技巧:让进程在后台可靠运行的几种方法
探究
为了找到原因,我们对问题进行复现。
首先登录到远程 shell 中,执行sleep 3600 &
命令,我们可以通过jobs
看到该命令在后台运行。
root@VPS-WRAY:~# sleep 3600 & [1] 23960 root@VPS-WRAY:~# jobs [1]+Runningsleep 3600 &
利用ps
命令查看进程状态:
root@VPS-WRAY:~# ps -Hf UIDPIDPPIDC STIME TTYTIME CMD root23946 239440 16:13 pts/000:00:00 -bash root23960 239460 16:13 pts/000:00:00sleep 3600 root23961 239460 16:13 pts/000:00:00ps -Hf
可以看到 sleep 进程的 ppid 是 23946,也就是说它的父进程是 bash。
由于在创建该任务时并没有使用nohup
命令,所以当退出 shell 时,该任务应该会收到由 shell 发送的 SIGHUP 信号,从而结束。
我们退出后再次登录远程 Shell,查看进程状态:
root@VPS-WRAY:~# ps -ef | grep sleep root2396010 16:13 ?00:00:00 sleep 3600 root24003 239930 16:27 pts/100:00:00 grep sleep
sleep 并没有结束!并且它的父进程变成了 init 进程(ppid = 1)。
我们看看手动发送 SIGHUP 信号能否结束 sleep 进程:
root@VPS-WRAY:~# kill -s SIGHUP 23960 root@VPS-WRAY:~# ps -ef | grep sleep root24077 239930 16:35 pts/100:00:00 grep sleep
sleep 进程被终止了。
这说明了问题出在 shell 上,当我们退出 shell 时,bash 进程终止了,但它并没有给子进程 sleep 发送 SIGHUP 信号,于是 sleep 进程变成了孤儿进程,然后过继给 init 进程,所以 ppid 变为了 1,继续在系统中运行。
但是,shell 在退出时不是会自动发送 SIGHUB 信号吗?
原因
以下段落引自BASH man page :
The shell exits by default upon receipt of aSIGHUP . Before exiting, an interactive shell resends theSIGHUP to all jobs, running or stopped. Stopped jobs are sentSIGCONT to ensure that they receive theSIGHUP . To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with thedisown builtin (seeSHELL BUILTIN COMMANDS below) or marked to not receiveSIGHUP usingdisown -h .
If thehuponexit shell option has been set withshopt ,bash sends aSIGHUP to all jobs when an interactive login shell exits.
也就是说,在huponexit
选项被设置了的情况下,交互式的 shell 在退出前会发送 SIGHUP 信号给所有任务。但是,bash 中该选项默认是没有设置的,因此在退出时不会向子进程发送 SIGHUP 信号。
通过shopt -p
命令可以列出 shell 选项,-u
表示未被设置,-s
表示被设置:
root@VPS-WRAY:~# shopt -p | grep huponexit shopt -u huponexit
该选项处于未被设置的状态,这解释了为什么在退出 shell 时,普通后台任务还能继续运行。
我们可以通过shopt -s huponexit
命令来设置该选项,将其写入到/etc/profile
中可以确保每次退出 shell 时向后台任务发送 SIGHUP 信号,进而终止后台任务。
当然,从另一个角度来看,这也是实现后台长期执行任务的一种方法,不过相比之下,从一开始就使用nohup
、disown
等命令来实现会更稳妥一些。