Linux让进程后台运行且连接断开不影响(nohup、setsid、disown、screen)

2019-02-14 09:53:20   Linux

背景

由于Linux的某些特性,例如可以多用户同时登陆、服务器运用广泛等,我们通常会用ssh去连接一台远程的Linux主机,或者在开发机上(本机)开多个terminal。而在我们运行一个耗时较长的任务时,如果因为网络原因,或其他未知异常导致终端连接断开,那我们的任务随即也会被kill掉。这是因为当你连接断开的时候,终端会收到一个SIGHUP信号,进而终端当前进程下的所有子进程,所以与之相关的任务将全被kill。而我们很多情况下想要的是在意外发生的时候,任务继续跑而不受父进程的影响。

元凶:SIGHUP 信号

让我们来看看为什么关掉窗口/断开连接会使得正在运行的程序死掉。 在Linux/Unix中,有这样几个概念: 进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组ID,即进程组长进程的ID。 会话期(session):一个或多个进程组的集合,有唯一一个会话期首进程(session leader)。会话期ID为首进程的ID。 会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其余进程组称为后台进程组。 根据POSIX.1定义: 挂断信号(SIGHUP)默认的动作是终止程序。 当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)。 如果会话期首进程终止,则该信号发送到该会话期前台进程组。 一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中所有进程。 因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出。


解决方案

未雨绸缪派

nohup

通过背景分析,我们很容易想到,如果捂住终端的眼睛(让终端忽略SIGHUP信号),是不是就不会干掉子进程了?nohup就是干这样一件事情,其用法也很简单,命令前加上nohup即可:

nohup command &

示例:

➜  ~ nohup python -m SimpleHTTPServer 8421 &
[1] 7533
appending output to nohup.out
➜  ~ ps -ef | grep 7533
  501  7533  6838   0  9:39AM ttys003    0:00.06 python -m SimpleHTTPServer 8421
  501  7613  6838   0  9:39AM ttys003    0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 7533
➜  ~ ls -al nohup.out
-rw-------  1 vien  staff  0 Aug 30 09:39 nohup.out

说明: 1.后缀&是让其后台运行,但注意,后台运行不代表不受SIGHUP信号影响,连接断开的话依然会终止任务。 2.我们可以看到下面的appending output to nohup.out,然后在最后我列出了这个文件,可以看到就是刚刚创建的。任务所有的输出都将输出到这个文件中,当然也可以自定输出路径、文件名、内容等。 3.然后中间我列出了这个进程,可以观察到,其父进程ID与当前窗口下的进程ID是相同的6838 ,也就是说这个任务还是隶属于当前进程,这将与下面的方法有所不同。

重定向输出位置:nohup command > vienout.log 2>&1 & 由于使用nohup时,会自动将输出写入nohup.out文件中,如果文件很大的话,nohup.out就会不停的增大,我们可以利用Linux下一个特殊的文件/dev/null来解决这个问题,这个文件就相当于一个黑洞,任何输出到这个文件的东西都将消失 只保留输出错误信息 nohup command >/dev/null 2>err.log & 所有信息都不要 nohup command >/dev/null 2>&1 &

这里解释一下后面的2>&1 。 这涉及到Linux的重定向,其中0、1、2分别是标准输入、标准输出、标准错误输出,用来指定需要重定向的标准输入输出。默认情况下是标出输出,也就是1 。例如我们而上文提到的 2>&1 是 将错误信息重定向到标准输出。

setsid

上面讲的忽略SIGHUP信号,那如果我们给它(任务)换个爹(父进程)呢?这时候我们就可以用到setsid ,用户与上一个一样简单:

setsid command

示例:

➜  ~ setsid ping google.com
...(省略输出)
➜  ~ ps -ef | grep ping
moma      6186     1  0 09:55 ?        00:00:00 ping google.com
moma      8681  7988  0 09:59 pts/37   00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn ping

说明: 1.我们可以看到这里任务的父进程ID变成了1,也就是init进程,这样它爹就不会轻易挂掉了。 2.之所以省略了若干输出,是因为输出太多了,没有重定向导入/dev/null或者别的地方,可想而知,人家亲爹不要,换个了有钱的干爹,你再骂他说:“你个龟儿子,不听老子话,就不给你零花钱”,当然不会鸟你了,也就是你的Ctrl+C之类的命令都是不可用的,你签字(回车)的那一刻,这儿子就归别人了。

说明:setsid命令在Mac系统下是不存在的

() + &

江湖中传闻一个关于 subshell 的小技巧。我们知道,将一个或多个命名包含在“()”中就能让这些命令在子 shell 中运行中,从而扩展出很多有趣的功能。 当我们将"&"也放入“()”内之后,我们就会发现所提交的作业并不在作业列表中,也就是说,是无法通过jobs来查看的。

(ping command &)

这里就不做示例,通过上面两个例子这个很容易就是用了。而且这个基本跟第二个一样,也是换爹

亡羊补牢派

刚刚讲的那些都是任务开始前搞事情,那么万一我们脑子一抽,手一抖,没有任何安全措施就一下子回了车呢?莫慌,还有这种操作。

disown

使某个作业忽略SIGHUP信号

disown jobspec

示例:

➜  ~ python -m SimpleHTTPServer 8877 &
[1] 20815
➜  ~ Serving HTTP on 0.0.0.0 port 8877 ...

➜  ~ jobs
[1]  + running    python -m SimpleHTTPServer 8877
➜  ~ disown %1
➜  ~ jobs
➜  ~

可以看到进程ID 20815前面有一个数字,加一个%即可

说明:这个命令是对作业(job)的操作,所以呢,首先你得把它变成job,很简单,如果你在运行前命令后面加了& 那恭喜你,现在他就是一个job,如果没有呢,也不要急,按下Ctrl+z 就可以把它挂到后台,成为一个job,此时我们可以通过jobs命令来查看所有的job。 关于job还有一些操作,bg %n是将job后台运行,fg %n是将job拿到前台运行,kill %n是杀掉这个job

需要注意的是,当使用过 disown 之后,会将把目标作业从作业列表中移除,我们将不能再使用jobs来查看它,但是依然能够用ps -ef查找到它。

不知道什么反正很吊派

screen 提供了 ANSI/VT100 的终端模拟器,使它能够在一个真实终端下运行多个全屏的伪终端。

screen

建立一个新session

screen

列出所有session

screen -ls 

重新连接指定session

screen -r screen_pid

用快捷键CTRL + ad 来暂时断开当前session,CTRL + d 结束当前session。 示例:

➜  ~ screen
(此时跳转到新窗口,按Ctrl+ad暂时断开)
➜  ~ screen -ls
There is a screen on:
        31665.pts-37.MBP        (2017年08月30日 10时59分49秒)   (Attached)
1 Socket in /var/run/screen/S-moma.
➜  ~ screen -r 31665
(恢复到screen_pid为31665的session)
➜  ~ ping google.com &
[1] 2184
(Ctrl+ad离开此session,然后查看进程树)
➜  ~ pstree -H 2184
systemd─┬─ModemManager─┬─{gdbus}
        │              └─{gmain}
        ├─NetworkManager─┬─dhclient
        │                ├─dnsmasq
        │                ├─{gdbus}
        ...
        ├─rtkit-daemon───2*[{rtkit-daemon}]
        ├─screen───zsh───ping
        ├─sshd───sshd───sshd───zsh─┬─pstree
        │                          └─3*[python]
        ├─2*[systemd───(sd-pam)]
        ...
(此时我们再连接回刚刚的session可以看到还是在运行的)

说明: 1.通过上面的进程树,我们可以很容易观察到,screen───zsh───ping是直接挂在systemd下的,这也就是不会受SIGHUP信号影响的原因,这跟setsid的原理是一样的,前文提到是init进程实在Linux下的,当前是systemd是因为在实在Mac系统下操作的。 2.还有就是这个命令如果机器上没有的话需要安装,Ubuntu的话是sudo apt-get install screen 3.这个命令是十分强大的,这里只是简单介绍,还有很多神奇的操作,想要玩爽还得自己用man screen去看

参考和引用

Linux 后台运行

我们常常会用终端连接Linux服务器,然后在运行类似Tomcat 、Web Logic等 web容器的时候希望退出终端依然可以运行。

我们可以通过 nohup command & 来使程序后台运

  • 以Tomcat为例
nohup ./startup.sh &

然后在shell中提示了nohup成功后:

nohup: ignoring input and appending output to ‘nohup.out’

然后按键盘任意键,回到shell输入命令窗口,然后在shell中输入

exit

退出终端,这时候,你的Tomcat就作为后台服务挂在Linux上了。

注意:不要执行完nohup直接点击关闭程序关闭终端,这样会干掉该命令的session,导致nohup对应的进程被通知一起被干掉,从而导致后台运行失败。

上文提到过nohup成功后的提示:nohup: ignoring input and appending output to ‘nohup.out’ 默认情况下nohup的作业的所有输出会被重定向到 nohup.out这个文件中,当然,你也可以指定输出:

nohup command > vienout.txt 2>&1 &

由于使用nohup时,会自动将输出写入nohup.out文件中,如果文件很大的话,nohup.out就会不停的增大,我们可以利用Linux下一个特殊的文件/dev/null来解决这个问题,这个文件就相当于一个黑洞,任何输出到这个文件的东西都将消失 只保留输出错误信息 nohup command >/dev/null 2>log & 所有信息都不要 nohup command >/dev/null 2>&1 &

这里解释一下后面的2>&1 。 这涉及到Linux的重定向,其中0、1、2分别是标准输入、标准输出、标准错误输出,用来指定需要重定向的标准输入输出。默认情况下是标出输出,也就是1 。例如我们而上文提到的 2>&1 是 将错误信息重定向到标准输出。

还有就是如果不想让程序输出,Linux下有一个/dev/null的特殊文件,就像一个黑洞,所有输出到这个文件的信息全部会消失,如果你不需要输出日志,这样做就不会导致输出日志文件越来越大,占用存储空间的问题了

关于Linux,新手想继续了解一些知识的话,推荐看一下《鸟哥的Linux私房菜》,讲的简单易懂,适合入门和作为工具书平时查阅

附:

  • ctrl+c #结束当前任务
  • ctrl+z #挂起当前任务
  • jobs -l #查看任务,返回任务编号 和 进程号
  • bg %n #编号n的任务转向后台运行,实际上bg n 也可以
  • fg %n #编号n的任务转向前台运行

转载请注明来源:

更多:免费搭梯子(ss+outline)教程

vien.tech版权所有,允许转载,但转载请注明出处和原文链接: https://viencoding.com/article/6
欢迎小伙伴们在下方评论区留言 ~ O(∩_∩)O
文章对我有帮助, 点此请博主吃包辣条 ~ O(∩_∩)O

猜你喜欢


评论

There are no comments yet.
未登录

登录后即可发表评论

登录或注册

亲情非友情链接