在 Linux 的终端中运行某些命令时,我们往往希望让它们在后台运行稳定运行而不受本地关闭终端窗口或网络断开连接的干扰。
先来了解一下:当用户注销(logout)或者网络断开时,终端会收到 SIGHUP
信号从而关闭其所有子进程。即 用户准备退出 session 时,系统向该 session 发出SIGHUP
信号,session 将SIGHUP
信号发给所有子进程,子进程收到SIGHUP
信号后,自动退出。
因此,要让命令在后台运行稳定的运行有两种途径:
- 让进程忽略
SIGHUP
信号;我们可以使用nohup
命令或disown
命令,同时配合&
一起使用。 - 或让进程运行在新的会话里从而成为不属于此终端的子进程;我们可以使用
setsid
命令
如果你不想看完全文,那么只需记住下面这两个最常用的方法即可,其中 command
表示你要在后台稳定运行的命令:
|
|
使用 & 开启后台任务
在类UNIX系统的命令行模式下,用户可使用 &
操作符以启动进程并使之运行于后台。
但后台任务继承当前 session(对话,就是终端窗口)的标准输出(stdout)和标准错误(stderr);因此,后台任务的所有输出依然会同步地在命令行下显示。但它不再继承当前session的标准输入(stdin)。
对比后面见到的守护进程的创建要求,可以看出单单使用
&
是不能保证命令在各类场景中一直在后台稳定运行的。
前后台进程的切换:
- 按
ctrl + z
将当前进程挂起到后台暂停运行 - 【可选】通过
jobs
命令来得到所有作业的任务号 - 用
bg
来将挂起的进程放在后台继续运行 - 用
fg
来将后台作业(在后台运行的或者在后台挂起的作业)放到前台终端运行。
后台进程的进程组ID(即PGID)与控制终端进程组ID(即TPGID)不同,因而也可以此辨识后台进程。我们可以验证一下:
|
|
|
|
disown命令
disown
命令。它可以将指定任务从"后台任务" 列表(jobs
命令的返回结果)之中移除。一个 “后台任务” 只要不在这个列表之中,session 就肯定不会向它发出SIGHUP
信号。
命令示例:
|
|
用法:
-
如果提交命令时已经用
&
将命令放入后台运行,则可以直接使用disown
,比如:1 2
$ node server.js & $ disown
-
如果提交命令时未使用
&
将命令放入后台运行,可使用CTRL-z
和bg
将其放入后台,再使用disown
注意:
使用disown
命令之后,还有一个问题。那就是,退出 session 以后,如果后台进程与标准I/O有交互,它还是会挂掉。
这是因为"后台任务"的标准 I/O 继承自当前 session,disown
命令并没有改变这一点。一旦"后台任务"读写标准 I/O,就会发现它已经不存在了,所以就报错终止执行。
为了解决这个问题,需要对"后台任务"的标准 I/O 进行重定向。比如:
|
|
nohup 命令
还有比disown
更方便的命令,就是nohup
。
nohup
的原理:
- 阻止
SIGHUP
信号发到这个进程。 - 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
- 重定向标准输出和标准错误到当前目录的
nohup.out
文件。
也就是说,nohup
命令实际上将子进程与它所在的 session 分离了。
注意,nohup
命令不会自动把进程变为"后台任务",所以必须加上&
符号。
常见的两种用法:
|
|
setsid 命令
如果我们的进程不属于接受 SIGHUP
信号的终端的子进程,那么自然也就不会受到 SIGHUP
信号的影响了。setsid
就能帮助我们做到这一点。
用法:也只需在要处理的命令前加上 setsid
即可。示例:
|
|
另可参考下文中 setsid 函数的作用。
补充:各种任务
-
前台任务(foreground job)是独占命令行窗口的任务,只有运行完了或者手动中止该任务,才能执行其他命令。
-
后台任务(background job),是一种在不需用户干预的情况下运行于操作系统后台的任务。后台任务继承当前 session(对话,就是终端窗口)的标准输出(stdout)和标准错误(stderr);因此,后台任务的所有输出依然会同步地在命令行下显示。但它不再继承当前session的标准输入(stdin)。
-
守护进程(daemon)是 Linux 中的后台服务进程,它是一个特殊的孤儿进程,这种进程脱离终端,这使得它可以避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。守护进程程序的名称通常以字母"d"结尾。
守护进程的创建步骤:
- 创建子进程,终止父进程。使得程序以僵尸进程形式运行,在形式上做到了与控制终端的脱离。
- 在子进程中创建新会话(最重要)。需要使用系统函数
setsid()
,该函数的三个作用:让进程摆脱原会话的控制、让进程摆脱原进程组的控制和让进程摆脱原控制终端的控制。 在调用fork函数时,子进程全盘拷贝父进程的会话期(session,是一个或多个进程组的集合)、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。 - 改变工作目录
- 重设文件创建掩码
- 关闭文件描述符
补充:命令的重定向
先看下面两个示例:
|
|
/dev/null
:代表空设备文件,写入到它的内容都会被丢弃。>
:代表重定向到哪里,例如:echo “123” > /home/123.txt0
: 表示标准输入 (STDIN)1
:表示标准输出(STDOUT),系统默认值是1,所以>/dev/null
等同于1>/dev/null
2
:表示标准错误输出(STDERR)&
:表示等同于的意思,2>&1
,表示2的输出重定向等同于1
cmd >a 2>a
和 cmd >a 2>&1
为什么不同?
cmd >a 2>a
:stdout和stderr都直接送往文件 a ,a文件会被打开两遍,由此导致stdout和stderr互相覆盖。cmd >a 2>&1
:stdout直接送往文件a ,stderr是继承了FD1的管道之后,再被送往文件a 。a文件只被打开一遍,就是FD1将其打开。
补充:ps命令
ps 命令的基本用法:
ps程序(“process status"的简称)可以显示当前运行的进程。一个相关的Unix工具top则可以查看运行进程的实时信息
ps有很多选项。在支持SUS和POSIX标准的操作系统上,ps常以选项-ef运行,其中”-e"选择每一个(every)进程,"-f"指定"完整"(full)输出格式。这些系统上的另一个常见选项是-l,它指定"长"(long)输出格式。
由于历史原因,大多数源自BSD的系统无法接受SUS和POSIX的标准选项(例如,“e"或”-e"选项将显示环境变量)。在这样的系统中,ps常使用辅助非标准选项aux,其中"a"列出了一个终端上的所有进程,包括其他用户运行的,“x"列出所有没有控制终端的进程,“u"添加了一列显示每个进程的控制用户。需要注意的是,为了最大的兼容性,使用此语法时"aux"前没有”-"。此外,在aux之后添加"ww"可以显示进程的完整信息,包括所有的参数,例如"ps auxww”。
示例:
1 2 3 4 5 6 7
$ps # ps命令和grep结合 $ ps -A | grep firefox-bin # 或直接使用pgrep $ pgrep -l firefox-bin # 查看以root用户运行的进程 ps -U root -u
如何查看守护进程?
使用 ps axj
:
a
表示不仅列当前用户的进程,也列出所有其他用户的进程x
表示不仅列有控制终端的进程,也列出所有无控制终端的进程j
表示列出与作业控制相关的信息
从上面命令的结果中可以发现一些 守护进行特点:
- 守护进程基本上都是以超级用户启动( UID 为
0
) - 没有控制终端( TTY 为
?
) - 终端进程组 ID 为 -1 (
TPGID
表示终端进程组 ID)