Linux下进程后台运行的方法

我们经常会碰到这样的问题,用telnet/ssh登录到远程的Linux服务器,运行了一些耗时较长的任务,由于网络不稳定,连接断开,就会导致任务中途失败。如何让程序在运行的时候不受本地终端窗口关闭或网络断开的干扰呢?

当用户注销或网络断开时,终端会受到HUP(hangup)信号,这个信号会向下传递给所有的子进程。如果没有人为处理HUP信号,默认情况下,收到这个信号会导致进程终止。

当我通过ssh登录到Linux服务器,我所在的bash的进程树是这样的:

sshd -D
  ├─sshd
  │   └─sshd
  │       └─bash
  │           └─pstree 111642 -a

我们在终端上运行的程序都是以这个bash的子进程在运行。当用户注销或网络断开时,bash会收到HUP信号,此信号会传递给所有的子进程,从而导致进程终止。

要想避免此问题,主要有两种方法。一种就是让进程忽略HUP信号(nohup就是采用的这种方式);另一种是让新运行的命令不是bash的子进程,这样就不会收到HUP信号了(setsid就是采用的这种方式)。

nohup

最常见的是nohup,使用非常方便。

示例:

nohup ping www.ibm.com &

标准输出和标准错误会被输出到当前目录下的 nohup.out 中,也可以自己重定向。一般会配合&使用,将任务放在后台运行。

nohup的原理其实很简单,主要是调用signal忽略HUP信号,然后调用 execvp 执行具体的程序。

下面贴一下网上找到的源码:

int main( int argc, char **argv )
{
	/* …… */
	if ( ignoring_input )
	{
		/* 重定向标准输入到/dev/null */
	}

	if ( redirecting_stdout || (redirecting_stderr && stdout_is_closed) )
	{
		/* 重定向标准输出到文件 */
	}

	if ( redirecting_stderr )
	{
		/* 重定向标准错误到文件 */
	}

	/* 忽略 SIGHUP 信号 */
	signal( SIGHUP, SIG_IGN );

	/* 执行 cmd */
	char **cmd = argv + optind;
	execvp( *cmd, cmd );
	/* …… */
	return(exit_status);
}

setsid

setsid会让新运行的程序的父进程变为1,从而不会收到HUP信号。

示例:

setsid ping www.ibm.com

可见 setsid 的使用也是非常方便的,也只需在要处理的命令前加上 setsid 即可。

(&) 的小技巧

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

当我们将”&”也放入“()”内之后,我们就会发现所提交的作业并不在作业列表中,也就是说,是无法通过jobs来查看的 ,而且作业也不受HUP信号的影响了。这是因为这样的命令的父进程不是当前的shell,而是进程号为1的进程(init或systemd)。

示例:

(ping www.ibm.com &)

disown

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