我们通过SSH连接Linux后,运行了一些程序,有时,程序耗时比较久,想着将程序切为后台程序后,退出SSH,一段时间后再连接,会发现,后台程序可能挂了。
基于这个现象,我在同事的帮助下,找到了一些解释,这里的一些细节,很多人都没有关注到。
如果一个程序正在运行,我们想将它切到后台运行,则可以使用bg命令。
为了方便演示,这里写一个简单的python代码,作为被操作的进程:
# mysleep.py
import time
import datetime
import os
def log_time(data):
log_file = 'log_time.txt'
if os.path.exists(log_file):
with open(log_file, 'a') as f:
f.write(data + '\n')
else:
with open(log_file, 'w') as f:
f.write(data + '\n')
for _ in range(1000):
d = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_time(d)
time.sleep(10)
脚本的逻辑非常简单,就是休眠10s,记录一下当前时间。
运行mysleep.py,先通过ctrl+z将当前命令挂起,然后再使用bg命令将程序切换到后台执行,通过jobs命令可以看见当前任务。
观察jobs命令的输出,可以发现,bg命令切换到后台的效果,其实就是使用了&语法,因为程序已经在运行状态,无法直接使用&语法,所以使用bg命令来实现。
当我们关闭当前SSH时,会发现进程被关闭了,没有继续在后台运行,经过Linux官方文档的查阅,bg命令与&语法的作用就是将前台进程放在后台运行。
那问题来了?为何我关闭当天SSH时,Linux会关闭我后台在运行的进程呢?
此外,还有另外一个更奇怪的,如果我直接通过关闭窗口的形式来断开SSH连接,重新连接SSH后,会发现,原本的mysleep程序被关闭了,但当我们使用exit命令退出当前SSH连接后,再连接SSH时,会发现mysleep没有被关闭。
bg命令与&语法构建的后台进程是否会被kill还跟关闭SSH的方式有关?
在同事的帮助下,发现导致bg命令与&语法这种现象的核心原因是Linux中的SIGHUP挂断信号,当用户正常退出(exit命令)SSH连接时,Linux不会发出SIGHUP信号,那么mysleep进程不会被kill,但如果用户通过直接关闭窗口的形式来断开SSH连接时,Linux会发出SIGHUP信号,从而关停SSH进程下所有的子进程。
嗯,口说无凭,这里我们直接打印出Linux发送信号的日志,来判断不同断开SSH时,是否有发送SIGHUP信号。
先看直接关闭窗口的形式的实验。
先通过cmd连接上服务器,然后运行mysleep.py。
我们通过 ps -ef 查看 mysleep 当前的PID(2016535)以及父进程的PID(2016481),因为我们在SSH中直接运行其mysleep.py,所以它的父进程就是当前终端进程。
我们可以通过下面命令打印当前终端进程PID:
echo $$
可知,终端PID就是2016481。
我们开启先的cmd再连接上服务器,通过下面命令,监控某进程收到signal信息。
strace -e signal -p pid
关闭2016481终端后,获得的日志如下:
将其复制到VSCode上,然后搜索一下mysleep进程pid,可以发现,直接关闭cmd窗口的形式,mysleep进程与父进程都收到了SIGHUP挂断信号并触发了kill。
这便是你通过bg命令或&语法将进程切到后台后,关闭SSH时依据会被kill掉的原因。
当我们通过exit命令退出当前SSH时,通过日志查看,会发现进程不会收到SIGHUP挂断信号,mysleep进程不会退出,但终端进程会被关闭。
那问题又来了,mysleep进程的父进程就是终端进程,如果终端进程被关闭,mysleep进程新的父进程会是哪个?
经过实验,父进程会变为1。
为了避免用户直接关闭窗口导致mysleep进程被关闭的情况,是否还有其他方法让进程在后台运行。
有!
nohup就是很常见的解决方法
nohup python mysleep.py &
&语法将程序切到后台运行,nohup命令让进程永远执行下去,不受SIGHUP挂断信号影响。
nohup命令的问题在于,只能对未执行的进程操作,假设我现在在使用scp传输文件,文件很大,此时我想将scp进程切换到后台,nohup命令便无法做到。
似乎只能通过bg命令来弄?其实还有另辟蹊径的解决方法,比如使用screen。
screen是第三方实现的一个终端窗口管理器,我们通过SSH连接上后,再使用screen创建新的终端窗口,这相当于构建了独立的终端进程,它不受SIGHUP挂断信号影响。
除screen外,还有很多终端管理工具都有类似的功能,比如知名的tmux。
利用这种方案,我们可以将正在运行的进程切换到后台,并且不会让这些进程受到SIGHUP挂断信号影响。
以screen为例,输入screen进入screen命令环境,然后执行python mysleep.py,此时mysleep程序会正常执行,此时通过ctrl+a+d快捷键让screen切入到后台运行,此时在screen进程中运行的mysleep进程自然会在后台运行。
通过screen -ls可以查看后台运行中的screen,然后通过screen -R pid回到对应的screen
这类问题其实比较典型:我们无法知道内部的细节,因为没有了解过Linux这块的实现代码,也没有进行Debug。如果单纯看网上的文章,可以有一些了解,但总感觉虚,因为不知道真实情况是否如网上所说。
而本文的破局点在于strace -e signal -p pid命令可以打印出相关的细节,从而在实操中确认其他文章中提到的观点。
在研究的过程中,有个坑,我使用了Tabby这个知名Github终端项目。Tabby在你直接关闭窗口时,会帮你通过exit形式关闭,导致无法复现bg命令的效果,被它搞蒙了一会。
嗯,本文就这样啦,我是二两,我们下篇文章见。