Linux终端关闭后台进程也结束原因分析和nohup的使用


Windows和Linux的远程连接(都叫做远程连接吧)不同:

1)Windows远程连接后,如果在远程机器上运行某个程序,完全可以退出连接后过一段时间再连接到那台机器上看有没有结束,即Windows的远程连接在不经过任何处理的情况下,在远程机器上运行的程序不会随着中止连接而结束。

2)Ubuntu在终端登陆远程主机并运行程序后,如果此时直接中止连接退出,那么这个连接所开的会话(session)下运行的所有进程都会被杀死,即在远程运行的程序会随着中止连接而结束。

Windows就此功能来说,方便使用,Ubuntu如果在使用上能够达到Windows的响应功能特性将会大大方便工作。

Ubuntu中之所以会这样,主要因为:

1)进程的相关概念:

在Linux中,每个进程都属于一个进程组(group),进程组有一个组长;多个进程组构成一个会话,会话是由其中的进程建立的,该进程叫做会话的领导进程(session leader)。会话领导进程的PID成为识别会话的SID(session ID)。会话中的每个进程组称为一个工作(job)。会话可以有一个进程组成为会话的前台工作(foreground job),而其他的进程组是后台工作(background job)。并不是进程组中的每个进程都是job中的内容,job是由session进程直接的“儿子”组成的,但是当job中的进程又产生子进程的时候,子进程便不是job中的内容。

每个会话可以连接一个控制终端(control terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。当前台进程组或者说job中的最后一个进程结束后,后台的session控制进程自动切换至前端,由终端产生的信号,比如CTRL+Z, CTRL+\,会传递到前台进程组。

会话主要是针对一个终端建立,当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。这样,我们就为进程增加了管理和运行的层次。

2)终端的相关概念:

在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal),控制终端是保存在PCB中的信息,而我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-表示SIGQUIT。

每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。在linux上的命令tty 也可以查看到当前的终端。比如我们在图形界面下打开一个终端可能是/dev/pts/0, 第二个可能是/dev/pts/1 .. (网络终端),而切换到字符界面下可能是/dev/tty1 ...(虚拟终端)

3)进程和终端:

用户通过终端登录系统后得到一个Shell进程,Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制(JobControl)。例如用以下命令启动5个进程(这个例子出自APUE):
$ proc1 | proc2 &

$ proc3 | proc4 | proc5

  其中proc1和proc2属于同一个后台进程组,proc3、proc4、proc5属于同一个前台进程组,Shell进程本身属于一个单独的进程组。这些进程组的控制终端相同,它们属于同一个Session,一个Session与一个控制终端相关。当用户在控制终端输入特殊的控制键(例如Ctrl-C)时,内核会发送相应的信号(例如SIGINT)给前台进程组的所有进程。各进程、进程组、Session的关系如下图所示。

\

在上面的例子中,proc3、proc4、proc5被Shell放到同一个前台进程组,其中有一个进程是该进程组的Leader,Shell调用wait等待它们运行结束。一旦它们全部运行结束,Shell就调用tcsetpgrp函数将自己提到前台继续接受命令。但是注意,如果proc3、proc4、proc5中的某个进程又fork出子进程,子进程也属于同一进程组,但是Shell并不知道子进程的存在,也不会调用wait等待它结束。换句话说,proc3 | proc4 | proc5是Shell的作业,而这个子进程不是,这是作业和进程组在概念上的区别。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程组还存在(如果这个子进程还没终止),则它自动变成后台进程,被init进程接管。

Linux的普通进程(守护进程除外)是终端的子进程,进程的存在要依赖终端为其提供空间包括标准输入、标准输出、标准出错。当打开终端的最初的进程(也就是SID进程)退出后,其子进程也会结束。

4)终端登录和执行命令的过程:

现在我们从Session和进程组的角度重新来看登录和执行命令的过程。

getty或telnetd进程在打开终端设备之前调用setsid函 数创建一个新的Session,该进程称为SessionLeader,该进程的id也可以看作Session的id,然后该进程打开终端设备作为这个Session中所有进程的控制终端。在创建新Session的同时也创建了一个新的进程组,该进程是这个进程组的Process Group Leader,该进程的id也是进程组的id。

在登录过程中,getty或telnetd进程变成login,然后变成Shell,但仍然是同一个进程,仍然是Session Leader。

由 Shell进程fork出的子进程(比如说上例中的p3,p4,p5)本来具有和Shell相同的Session、进程组和控制 终端,但是Shell调用setpgid函数将作业中的某个子进程指定为一个新进程组的 Leader(比如说p3),然后调用setpgid将该作业中的其它子进程也转移到这个进程组中。如果这个进程组需要在前台运行,就调用tcsetpgrp函数将它设置为前台进程组,由于一个 Session只能有一个前台进程组,所以Shell所在的进程组就自动变成后台进程组。

5)终端退出进程继续执行的方法

Unix/Linux下一般比如想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。可能我们的程序只是普通程序而已(并不是守护进程),一般这种程序使用 & 结尾,但是如果终端关闭,那么程序也会被关闭。

使用nohup命令可以在后台执行程序并且终端退出仍然运行:

  nohup make &

  此时屏幕提示:

  [~]$ appending output to nohup.out

  证明运行成功,同时把程序运行的输出信息放到当前目录的 nohup.out 文件中去。

  记得在退出终端的时候应该采用正常的退出方法,而不是直接关掉putty或者其他终端登陆程序,应该在会话中exit。此点待考。

附:nohup命令参考

  nohup 命令

  用途:不挂断地运行命令。

  语法:nohup Command [ Arg ... ] [ & ]

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

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

其他方法参照让Linux的进程不受终端影响

相关内容