Linux中前台进程切换成后台进程的门道

511b28c5e1ed29e0a0df8328777664d1.jpeg

前言

我们通过SSH连接Linux后,运行了一些程序,有时,程序耗时比较久,想着将程序切为后台程序后,退出SSH,一段时间后再连接,会发现,后台程序可能挂了。

基于这个现象,我在同事的帮助下,找到了一些解释,这里的一些细节,很多人都没有关注到。

bg命令与&语法

如果一个程序正在运行,我们想将它切到后台运行,则可以使用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命令可以看见当前任务。

a3d252d462e2d1efcbfb7d8e0e8dbabe.png

观察jobs命令的输出,可以发现,bg命令切换到后台的效果,其实就是使用了&语法,因为程序已经在运行状态,无法直接使用&语法,所以使用bg命令来实现。

当我们关闭当前SSH时,会发现进程被关闭了,没有继续在后台运行,经过Linux官方文档的查阅,bg命令与&语法的作用就是将前台进程放在后台运行。

那问题来了?为何我关闭当天SSH时,Linux会关闭我后台在运行的进程呢?

此外,还有另外一个更奇怪的,如果我直接通过关闭窗口的形式来断开SSH连接,重新连接SSH后,会发现,原本的mysleep程序被关闭了,但当我们使用exit命令退出当前SSH连接后,再连接SSH时,会发现mysleep没有被关闭。

bg命令与&语法构建的后台进程是否会被kill还跟关闭SSH的方式有关?

SIGHUP挂断信号

在同事的帮助下,发现导致bg命令与&语法这种现象的核心原因是Linux中的SIGHUP挂断信号,当用户正常退出(exit命令)SSH连接时,Linux不会发出SIGHUP信号,那么mysleep进程不会被kill,但如果用户通过直接关闭窗口的形式来断开SSH连接时,Linux会发出SIGHUP信号,从而关停SSH进程下所有的子进程。

嗯,口说无凭,这里我们直接打印出Linux发送信号的日志,来判断不同断开SSH时,是否有发送SIGHUP信号。

先看直接关闭窗口的形式的实验。

先通过cmd连接上服务器,然后运行mysleep.py。

b0ae230133eba917cc2b67f9a84610dd.png

我们通过 ps -ef 查看 mysleep 当前的PID(2016535)以及父进程的PID(2016481),因为我们在SSH中直接运行其mysleep.py,所以它的父进程就是当前终端进程。

我们可以通过下面命令打印当前终端进程PID:

echo $$

可知,终端PID就是2016481。

我们开启先的cmd再连接上服务器,通过下面命令,监控某进程收到signal信息。

strace -e signal -p pid

关闭2016481终端后,获得的日志如下:

0d9b273dd2275ce44083de3b2116cb95.png

将其复制到VSCode上,然后搜索一下mysleep进程pid,可以发现,直接关闭cmd窗口的形式,mysleep进程与父进程都收到了SIGHUP挂断信号并触发了kill。

23fa3d59ec9c2904b89fa5e20df379bd.png

这便是你通过bg命令或&语法将进程切到后台后,关闭SSH时依据会被kill掉的原因。

当我们通过exit命令退出当前SSH时,通过日志查看,会发现进程不会收到SIGHUP挂断信号,mysleep进程不会退出,但终端进程会被关闭。

那问题又来了,mysleep进程的父进程就是终端进程,如果终端进程被关闭,mysleep进程新的父进程会是哪个?

经过实验,父进程会变为1。

dc83963c5a7ca3ad9817ce5b8418b46e.png65d8aff7ad8416ddd10f8c339ec64d1f.png

切换成后台进程的正常姿势

为了避免用户直接关闭窗口导致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进程自然会在后台运行。

52cf9976e06d8fdff364fde1764e58a4.png

通过screen -ls可以查看后台运行中的screen,然后通过screen -R pid回到对应的screen

结尾

这类问题其实比较典型:我们无法知道内部的细节,因为没有了解过Linux这块的实现代码,也没有进行Debug。如果单纯看网上的文章,可以有一些了解,但总感觉虚,因为不知道真实情况是否如网上所说。

而本文的破局点在于strace -e signal -p pid命令可以打印出相关的细节,从而在实操中确认其他文章中提到的观点。

在研究的过程中,有个坑,我使用了Tabby这个知名Github终端项目。Tabby在你直接关闭窗口时,会帮你通过exit形式关闭,导致无法复现bg命令的效果,被它搞蒙了一会。

c8e3c04c35c7dfefe45c69876bd3d64a.png

嗯,本文就这样啦,我是二两,我们下篇文章见。

猜你喜欢

转载自blog.csdn.net/weixin_30230009/article/details/126239330
今日推荐