由于Linux的某些特性,例如可以多用户同时登陆、服务器运用广泛等,我们通常会用ssh去连接一台远程的Linux主机,或者在开发机上(本机)开多个terminal。而在我们运行一个耗时较长的任务时,如果因为网络原因,或其他未知异常导致终端连接断开,那我们的任务随即也会被kill掉。这是因为当你连接断开的时候,终端会收到一个SIGHUP信号,进而终端当前进程下的所有子进程,所以与之相关的任务将全被kill。而我们很多情况下想要的是在意外发生的时候,任务继续跑而不受父进程的影响。
让我们来看看为什么关掉窗口/断开连接会使得正在运行的程序死掉。 在Linux/Unix中,有这样几个概念: 进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组ID,即进程组长进程的ID。 会话期(session):一个或多个进程组的集合,有唯一一个会话期首进程(session leader)。会话期ID为首进程的ID。 会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其余进程组称为后台进程组。 根据POSIX.1定义: 挂断信号(SIGHUP)默认的动作是终止程序。 当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)。 如果会话期首进程终止,则该信号发送到该会话期前台进程组。 一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中所有进程。 因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出。
通过背景分析,我们很容易想到,如果捂住终端的眼睛(让终端忽略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 是 将错误信息重定向到标准输出。
上面讲的忽略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 &)
这里就不做示例,通过上面两个例子这个很容易就是用了。而且这个基本跟第二个一样,也是换爹。
刚刚讲的那些都是任务开始前搞事情,那么万一我们脑子一抽,手一抖,没有任何安全措施就一下子回了车呢?莫慌,还有这种操作。
使某个作业忽略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 的终端模拟器,使它能够在一个真实终端下运行多个全屏的伪终端。
建立一个新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服务器,然后在运行类似Tomcat 、Web Logic等 web容器的时候希望退出终端依然可以运行。
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私房菜》,讲的简单易懂,适合入门和作为工具书平时查阅
附: