进程在后台运行原理


nohup/setsid/&
场景:
如果只是临时有一个命令需要长时间运行,什么方法能最简便的保证它在后台稳定运行呢?



hangup 名称的来由
在 Unix 的早期版本中,每个终端都会通过 modem 和系统通讯。当用户 logout 时,modem 就会挂断(hang up)电话。 同理,当 modem 断开连接时,就会给终端发送 hangup 信号来通知其关闭所有子进程。



解决方法:
我们知道,当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。因此,我们的解决办法就有两种途径:要么让进程忽略 HUP 信号,要么让进程运行在新的会话里从而成为不属于此终端的子进程。




  1. nohup



nohup 无疑是我们首先想到的办法。顾名思义,nohup 的用途就是让提交的命令忽略 hangup 信号。让我们先来看一下 nohup 的帮助信息:
NOHUP(1) User Commands NOHUP(1)



NAME
nohup - run a command immune to hangups, with output to a non-tty



SYNOPSIS
nohup COMMAND [ARG]…
nohup OPTION



DESCRIPTION
Run COMMAND, ignoring hangup signals.



   --help display this help and exit

--version
output version information and exit 可见,nohup 的使用是十分方便的,只需在要处理的命令前加上 nohup 即可,标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般我们可在结尾加上"&"来将命令同时放入后台运行,也可用">filename 2>&1"来更改缺省的重定向文件名。


nohup 示例
[root@pvcent107 ~]# nohup ping www.ibm.com &
[1] 3059
nohup: appending output to `nohup.out’
[root@pvcent107 ~]# ps -ef |grep 3059
root 3059 984 0 21:06 pts/3 00:00:00 ping www.ibm.com
root 3067 984 0 21:06 pts/3 00:00:00 grep 3059
[root@pvcent107 ~]#
2。setsid



nohup 无疑能通过忽略 HUP 信号来使我们的进程避免中途被中断,但如果我们换个角度思考,如果我们的进程不属于接受 HUP 信号的终端的子进程,那么自然也就不会受到 HUP 信号的影响了。setsid 就能帮助我们做到这一点。让我们先来看一下 setsid 的帮助信息:
SETSID(8) Linux Programmer’s Manual SETSID(8)



NAME
setsid - run a program in a new session



SYNOPSIS
setsid program [ arg … ]



DESCRIPTION
setsid runs a program in a new session.
可见 setsid 的使用也是非常方便的,也只需在要处理的命令前加上 setsid 即可。



setsid 示例
[root@pvcent107 ~]# setsid ping www.ibm.com
[root@pvcent107 ~]# ps -ef |grep www.ibm.com
root 31094 1 0 07:28 ? 00:00:00 ping www.ibm.com
root 31102 29217 0 07:29 pts/4 00:00:00 grep www.ibm.com
[root@pvcent107 ~]#
值得注意的是,上例中我们的进程 ID(PID)为31094,而它的父 ID(PPID)为1(即为 init 进程 ID),并不是当前终端的进程 ID。请将此例与nohup 例中的父 ID 做比较。



3。&



这里还有一个关于 subshell 的小技巧。我们知道,将一个或多个命名包含在“()”中就能让这些命令在子 shell 中运行中,从而扩展出很多有趣的功能,我们现在要讨论的就是其中之一。



当我们将”&”也放入“()”内之后,我们就会发现所提交的作业并不在作业列表中,也就是说,是无法通过jobs来查看的。让我们来看看为什么这样就能躲过 HUP 信号的影响吧。



subshell 示例
[root@pvcent107 ~]# (ping www.ibm.com &)
[root@pvcent107 ~]# ps -ef |grep www.ibm.com
root 16270 1 0 14:13 pts/4 00:00:00 ping www.ibm.com
root 16278 15362 0 14:13 pts/4 00:00:00 grep www.ibm.com
[root@pvcent107 ~]#
从上例中可以看出,新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此并不属于当前终端的子进程,从而也就不会受到当前终端的 HUP 信号的影响了。



disown
场景:
我们已经知道,如果事先在命令前加上 nohup 或者 setsid 就可以避免 HUP 信号的影响。但是如果我们未加任何处理就已经提交了命令,该如何补救才能让它避免 HUP 信号的影响呢?



解决方法:
这时想加 nohup 或者 setsid 已经为时已晚,只能通过作业调度和 disown 来解决这个问题了。让我们来看一下 disown 的帮助信息:
disown [-ar] [-h] [jobspec …]
Without options, each jobspec is removed from the table of
active jobs. If the -h option is given, each jobspec is not
removed from the table, but is marked so that SIGHUP is not
sent to the job if the shell receives a SIGHUP. If no jobspec
is present, and neither the -a nor the -r option is supplied,
the current job is used. If no jobspec is supplied, the -a
option means to remove or mark all jobs; the -r option without
a jobspec argument restricts operation to running jobs. The
return value is 0 unless a jobspec does not specify a valid
job.
可以看出,我们可以用如下方式来达成我们的目的。



灵活运用 CTRL-z
在我们的日常工作中,我们可以用 CTRL-z 来将当前进程挂起到后台暂停运行,执行一些别的操作,然后再用 fg 来将挂起的进程重新放回前台(也可用 bg 来将挂起的进程放在后台)继续运行。这样我们就可以在一个终端内灵活切换运行多个任务,这一点在调试代码时尤为有用。因为将代码编辑器挂起到后台再重新放回时,光标定位仍然停留在上次挂起时的位置,避免了重新定位的麻烦。



用disown -h jobspec来使某个作业忽略HUP信号。
用disown -ah 来使所有的作业都忽略HUP信号。
用disown -rh 来使正在运行的作业忽略HUP信号。
需要注意的是,当使用过 disown 之后,会将把目标作业从作业列表中移除,我们将不能再使用jobs来查看它,但是依然能够用ps -ef查找到它。



但是还有一个问题,这种方法的操作对象是作业,如果我们在运行命令时在结尾加了”&”来使它成为一个作业并在后台运行,那么就万事大吉了,我们可以通过jobs命令来得到所有作业的列表。但是如果并没有把当前命令作为作业来运行,如何才能得到它的作业号呢?答案就是用 CTRL-z(按住Ctrl键的同时按住z键)了!



CTRL-z 的用途就是将当前进程挂起(Suspend),然后我们就可以用jobs命令来查询它的作业号,再用bg jobspec来将它放入后台并继续运行。需要注意的是,如果挂起会影响当前进程的运行结果,请慎用此方法。



disown 示例1(如果提交命令时已经用“&”将命令放入后台运行,则可以直接使用“disown”)
[root@pvcent107 build]# cp -r testLargeFile largeFile &
[1] 4825
[root@pvcent107 build]# jobs
[1]+ Running cp -i -r testLargeFile largeFile &
[root@pvcent107 build]# disown -h %1
[root@pvcent107 build]# ps -ef |grep largeFile
root 4825 968 1 09:46 pts/4 00:00:00 cp -i -r testLargeFile largeFile
root 4853 968 0 09:46 pts/4 00:00:00 grep largeFile
[root@pvcent107 build]# logout
disown 示例2(如果提交命令时未使用“&”将命令放入后台运行,可使用 CTRL-z 和“bg”将其放入后台,再使用“disown”)
[root@pvcent107 build]# cp -r testLargeFile largeFile2



[1]+ Stopped cp -i -r testLargeFile largeFile2
[root@pvcent107 build]# bg %1
[1]+ cp -i -r testLargeFile largeFile2 &
[root@pvcent107 build]# jobs
[1]+ Running cp -i -r testLargeFile largeFile2 &
[root@pvcent107 build]# disown -h %1
[root@pvcent107 build]# ps -ef |grep largeFile2
root 5790 5577 1 10:04 pts/3 00:00:00 cp -i -r testLargeFile largeFile2
root 5824 5577 0 10:05 pts/3 00:00:00 grep largeFile2
[root@pvcent107 build]#
screen
场景:
我们已经知道了如何让进程免受 HUP 信号的影响,但是如果有大量这种命令需要在稳定的后台里运行,如何避免对每条命令都做这样的操作呢?



解决方法:
此时最方便的方法就是 screen 了。简单的说,screen 提供了 ANSI/VT100 的终端模拟器,使它能够在一个真实终端下运行多个全屏的伪终端。screen 的参数很多,具有很强大的功能,我们在此仅介绍其常用功能以及简要分析一下为什么使用 screen 能够避免 HUP 信号的影响。我们先看一下 screen 的帮助信息:
SCREEN(1) SCREEN(1)



NAME
screen - screen manager with VT100/ANSI terminal emulation



SYNOPSIS
screen [ -options ] [ cmd [ args ] ]
screen -r [[pid.]tty[.host]]
screen -r sessionowner/[[pid.]tty[.host]]



DESCRIPTION
Screen is a full-screen window manager that multiplexes a physical
terminal between several processes (typically interactive shells).
Each virtual terminal provides the functions of a DEC VT100 terminal
and, in addition, several control functions from the ISO 6429 (ECMA
48, ANSI X3.64) and ISO 2022 standards (e.g. insert/delete line and
support for multiple character sets). There is a scrollback history
buffer for each virtual terminal and a copy-and-paste mechanism that
allows moving text regions between windows.
使用 screen 很方便,有以下几个常用选项:



用screen -dmS session name来建立一个处于断开模式下的会话(并指定其会话名)。
用screen -list 来列出所有会话。
用screen -r session name来重新连接指定会话。
用快捷键CTRL-a d 来暂时断开当前会话。
screen 示例
[root@pvcent107 ~]# screen -dmS Urumchi
[root@pvcent107 ~]# screen -list
There is a screen on:
12842.Urumchi (Detached)
1 Socket in /tmp/screens/S-root.



[root@pvcent107 ~]# screen -r Urumchi
当我们用“-r”连接到 screen 会话后,我们就可以在这个伪终端里面为所欲为,再也不用担心 HUP 信号会对我们的进程造成影响,也不用给每个命令前都加上“nohup”或者“setsid”了。这是为什么呢?让我来看一下下面两个例子吧。





  1. 未使用 screen 时新进程的进程树
    [root@pvcent107 ~]# ping www.google.com &
    [1] 9499
    [root@pvcent107 ~]# pstree -H 9499
    init─┬─Xvnc
    ├─acpid
    ├─atd
    ├─2*[sendmail]
    ├─sshd─┬─sshd───bash───pstree
    │ └─sshd───bash───ping
    我们可以看出,未使用 screen 时我们所处的 bash 是 sshd 的子进程,当 ssh 断开连接时,HUP 信号自然会影响到它下面的所有子进程(包括我们新建立的 ping 进程)。




  2. 使用了 screen 后新进程的进程树
    [root@pvcent107 ~]# screen -r Urumchi
    [root@pvcent107 ~]# ping www.ibm.com &
    [1] 9488
    [root@pvcent107 ~]# pstree -H 9488
    init─┬─Xvnc
    ├─acpid
    ├─atd
    ├─screen───bash───ping
    ├─2*[sendmail]
    而使用了 screen 后就不同了,此时 bash 是 screen 的子进程,而 screen 是 init(PID为1)的子进程。那么当 ssh 断开连接时,HUP 信号自然不会影响到 screen 下面的子进程了。





生产环境下,除了我们ssh登录上去,然后手动跑的那部分以外,其他都是自动运行的,这些大部分都应该是后台执行的。如何才能后台执行呢?



nohup ./XXX &
由系统的其他daemon进程启动。这样的话,你的程序是它的子进程,跟终端没关系。退出终端也不会导致进程退出。如写在crontab里。
写成daemon程序,可以手动执行,退出终端时程序不退出。
如何选择呢?
(1)首先,清理过期日志这一类需求,可以写一个死循环一直运行,也可以写在crontab里,每次执行完就退出(如果每分钟一次可以满足的话);
(2)crontab的需要接受最多1分钟的时延,如果实时性要求更高一些,那么就需要考虑写个死循环了,这个程序可以由crontab来start和restart,只有在挂了重启时才会出现1分钟时延;
(3)服务不能中断的(nginx、redis、apache,所有在线服务),一般都是daemon程序。但理论上用(2)似乎也可以;当然这两者细节上有很多区别。



是nohup的关键代码了,首先向系统注册一个SIGHUP信号,处理方式是忽略,系统默认的方式kill进程,接着execvp函数执行参数后面的进程。



为什么这样可以做到把代码放到后台运行呢,不让进程不因SIGHUP信号而终止呢,得弄清楚什么时候系统会向进程发送SINHUP信号呢,其实在posix标准里,linux也遵守这个标准,当用户在连接上linux终端后,当当前会话终端中断时,系统会向当前会话的前台进程组发送一个SIGHUP信号,系统默认对这个信号的处理方式是KILL该进程,如果进程忽视这个信号,没有被kill的话,接着系统会再次向该进程发送一个SIGCONT信号,nohup的作用就是让程序忽视SIGHUP信号,避免终端连接中断时,进程被kill,同时改变进程标准输入输出。



其实有没有必要一定用nohup方式去启动后台进程呢,我觉得没有必要,当一个会话的终端中断后,系统只会给当前会话的前台进程组发送SIGHUP信号,如果启动进程时,让进程在后台去执行,也是执行的时候后面加一个&,也可以自己去重定向输入输出,会话的终端中断时,后台的进程组是不会收到SIGHUP信号,也就不会被系统kill掉,当然也可以在代码里注册SIGHUP该信号,防止进程被kill。
你可能会遇到nohup命令问题,这里将介绍nohup命令问题的解决方法,Linux本身是这个操作系统的核心部分,也就是操作系统的内核。内核 是完成那些最基本操作的程序,它负责其他程序(如文本编辑器程序)的启动与终止、内存申请处理硬盘访问、网络连接管理等方面的工作。Unix/Linux 下一般想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。



比如我们要运行mysql在后台:/usr/local/mysql/bin/mysqld_safe –user=mysql &但是我们很多程序并不象mysqld一样可以做成守护进程,可能我们的程序只是普通程序而已,一般这种程序即使使用 & 结尾,如果终端关闭,那么程序也会被关闭。为了能够后台运行,我们需要使用nohup命令,比如我们有个start.sh需要在后台运行,并且希望在后台 能够一直运行,那么就使用nohup:



nohup /root/start.sh &



在shell中回车后提示:
[~]$ appending output to nohup.out原程序的的标准输出被自动改向到当前目录下的nohup.out文件,起到了log的作用。但是有时候在这一步会有问题,当把终端关闭 后,进程会自动被关闭,察看nohup.out可以看到在关闭终端瞬间服务自动关闭。咨询红旗Linux工程师后,他也不得其解,在我的终端上执行后,他 启动的进程竟然在关闭终端后依然运行。



在第二遍给我演示时,我才发现我和他操作终端时的一个细节不同:他是在当shell中提示了nohup成功后还需要按终端上键盘任意键退回到 shell输入命令窗口,然后通过在shell中输入exit来退出终端;而我是每次在nohup执行成功后直接点关闭程序按钮关闭终端.。所以这时候会 断掉该命令所对应的session,导致nohup对应的进程被通知需要一起shutdown。这个细节有人和我一样没注意到,所以在这儿记录一下了。



附:nohup命令参考nohup命令
用途:不挂断地运行命令。
语法:nohup Command [ Arg … ] [ & ]
描 述:nohup命令运行由 Command 参数和任何相关的 Arg 参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup命令,添加 & ( 表示”and”的符号)到命令的尾部。



无论是否将 nohup命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out 文件中。如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。如果标准错误是一个终端,那么把指定的命令写给标准错误的所有输出作为标准输出重定向到相同的文件描述符。



退出状态:该命令返回下列出口值:126 可以查找但不能调用 Command 参数指定的命令。127 nohup 命令发生错误或不能查找由 Command 参数指定的命令。否则,nohup命令的退出状态是 Command 参数指定nohup命令的退出状态。



nohup命令及其输出文件



nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思( n ohang up)。



该命令的一般形式为:nohup command &使用nohup命令提交作业
如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件:
nohup command > myout.file 2>&1 &在上面的例子中,输出被重定向到myout.file文件中。使用 jobs 查看任务。使用 fg %n 关闭。



另外有两个常用的ftp工具ncftpget和ncftpput,可以实现后台的ftp上传和下载,这样就可以利用这些命令在后台上传和下载文件了。



unix中进程组织结构为session包含一个前台进程组及一个或多个后台进程组,一个进程组包含多个进程。



一个session可能会有一个session首进程,而一个session首进程可能会有一个控制终端。



一个进程组可能会有一个进程组首进程。



进程组首进程的进程与该进程组ID相等。



这儿是可能会有,在一定情况之下是没有的。



与终端交互的进程是前台进程,否则便是后台进程。



SIGHUP会在以下3种情况下被发送给相应的进程:



1、终端关闭时,该信号被发送到首进程以及作为job提交的进程(即用 & 符号提交的进程)



2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程



3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。



系统对信号的默认处理是终止收到该信号的进程。



所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。



下面观察几种因终端关闭导致进程退出的情况,在这儿进程退出是因为收到了SIGHUP信号。



login shell是session首进程。



编译后的执行文件为sigtest



1、命令: sigtest front > tt.txt



操作: 关闭终端



结果: tt文件的内容为front: sighup received



原因:



sigtest是前台进程,终端关闭后,根据上面提到的第1种情况, loginshell作为session首进程,会收到SIGHUP信号然后退出,



根据第2种情况,sigtest作为前台进程,会收到login shell发出的SIGHUP信号。



2、命令:



sigtest back > tt.txt &



操作:



关闭终端



结果:



tt文件的内容为back: sighup received



原因:



sigtest是提交的job,根据上面提到的第1种情况,sigtest会收到SIGHUP信号



3、写一个shell,内容为



[cpp] view plain copy
sigtest &

执行该shell



操作: 关闭终端










结果: ps -ef grep sigtest会看到该进程还在,tt文件为空


原因:



执行该shell时,sigtest作为job提交,然后该shell退出,致使sigtest变成了孤儿进程,不再是当前session的job了,



因此sigtest即不是session首进程也不是job,不会收到SIGHUP



同时孤儿进程属于后台进程,因此loginshell退出后不会发送SIGHUP给sigtest,因为它只将该信号发送给前台进程。



第3条说过若进程组变成孤儿进程组的时候,若有进程处于停止状态,也会收到SIGHUP信号,但sigtest没有处于停止状态,所以不会收到SIGHUP信号



4、nohup sigtest > tt



操作:



关闭终端



结果: tt文件为空



原因:nohup可以防止进程收到SIGHUP信号



至此,我们就清楚了何种情况下终端关闭后进程会退出,何种情况下不会退出。



要想终端关闭后进程不退出有以下几种方法,均为通过shell的方式:



1、 编写shell,内容如下



[cpp] view plain copy
trap “” SIGHUP #该句的作用是屏蔽SIGHUP信号,trap可以屏蔽很多信号



sigtest



2、nohup sigtest可以直接在命令行执行,



若想做完该操作后继续别的操作, 可以执行



[cpp] view plain copy
nohup sigtest &



3、 编写shell,内容如下



[cpp] view plain copy
sigtest &



其实任何将进程变为孤儿进程的方式都可以,包括fork后父进程马上退出



Category linux