如何在 Linux 上运行和控制后台进程

在Linux系统中,你可以使用Bash shell来管理前台和后台运行的程序。 Bash的作业控制功能和信号机制为你提供了更灵活的命令执行方式。 让我们一起深入了解如何利用这些功能。

进程概念详解

每当你在Linux或类Unix操作系统中启动一个程序,系统就会创建一个进程。“进程”是计算机内存在执行程序时的内部表示。每个正在运行的程序都有一个对应的进程。实际上,几乎你电脑上运行的所有内容都伴随着一个进程,包括图形桌面环境(如Gnome或KDE)以及系统启动时运行的守护进程。

为什么说是“几乎”所有?原因在于,像cdpwdalias这样的Bash内置命令在执行时不需要启动新的进程。Bash会在当前终端窗口的Bash shell实例中直接执行这些命令。这些命令之所以执行速度快,正是因为它们避免了创建新进程的开销。(你可以在终端窗口输入help来查看Bash内置命令列表。)

进程可以在前台运行,这种模式下它会独占你的终端直到完成;或者在后台运行,此时进程不会占用终端,你可以继续在终端中工作。当然,如果后台进程没有产生任何屏幕输出,它就不会干扰你的终端操作。

一个具体的演示

我们先从一个简单的ping命令开始,目标是www.wdzwdz.com域名。默认情况下,这将作为一个前台进程运行。

ping www.wdzwdz.com

正如我们所预料的,终端窗口会滚动显示ping的输出结果。 在ping运行时,我们无法在终端窗口中执行其他操作。要终止命令,你需要按下Ctrl+C

Ctrl+C

屏幕截图中高亮显示了Ctrl+C的效果。ping会给出一个简短的统计,然后停止运行。

现在我们再次运行ping命令,但这次我们会按下Ctrl+Z,而不是Ctrl+C。这次,任务不会被终止,而是被移动到后台。 终端的控制权将返回给你。

ping www.wdzwdz.com
Ctrl+Z

屏幕截图高亮显示了按下Ctrl+Z后的效果。

这次系统提示我们进程已停止。 停止并不等同于终止。 它就像一辆在停车标志处停下来的汽车。它没有被报废,它只是停下来等待再次启动。 该进程现在是一个后台作业。

可以使用jobs命令来查看当前终端会话中已启动的作业。因为作业本质上也是进程,我们也可以使用ps命令查看它们。 让我们使用这两个命令并比较输出。 我们将使用T(终端)选项,只列出在此终端窗口中运行的进程。 请注意,T选项前不需要使用连字符-

jobs
ps T

jobs命令的输出告诉我们:

[1]:方括号中的数字是作业编号。当我们需要使用作业控制命令来操作它时,可以使用这个编号来引用作业。
+:加号+表示,如果我们使用不带特定作业编号的作业控制命令,这个是将会被操作的作业。这被称为默认作业。默认作业始终是最近添加到作业列表中的作业。
已停止:表示进程当前没有运行。
ping www.wdzwdz.com:启动该进程的命令行。

ps命令的输出则告诉我们:

PID:进程的进程ID。 每个进程都有一个唯一的ID。
TTY:执行进程的伪终端(终端窗口)。
STAT:进程的状态。
TIME:进程消耗的CPU时间。
COMMAND:启动进程的命令。

以下是一些STAT列的常用值:

D:不可中断睡眠。进程正在等待某种事件,通常是I/O操作,且不能被中断。
I:空闲。
R:正在运行。
S:可中断睡眠。
T:被作业控制信号停止。
Z:僵尸进程。进程已终止,但其父进程尚未“清理”它。

STAT列的值后面可以跟以下附加标识之一:

<: 高优先级任务。
N:低优先级任务。
L:进程已将页面锁定到内存中。
s: 一个会话领导者。
+: 前台进程组的成员。
l:多线程进程。

我们可以看到Bash的状态为Ss。 大写的“S”告诉我们Bash shell处于休眠状态,而且是可中断的,只要我们需要,它就会响应。 小写的“s”则表明shell是一个会话领导者。

ping命令的状态为T,表明ping进程已被作业控制信号停止。 在这个例子中,就是我们用来将其放入后台的Ctrl+Z

ps T命令的状态为R,表示正在运行。+表示此进程是前台组的成员。 因此,ps T命令在前台运行。

bg 命令的用法

bg命令用于恢复后台进程的运行。 可以带或不带作业编号使用。如果不指定作业编号,则默认作业将被带到前台。进程仍然在后台运行,你无法向其发送任何输入。

如果我们执行bg命令,我们的ping命令会恢复运行:

bg

ping命令恢复运行,终端窗口再次开始滚动输出。系统也会显示重新启动的命令名称,这在屏幕截图中被高亮显示。

但现在我们面临一个问题。 该任务在后台运行,不接受输入。 那么我们如何才能停止它呢? Ctrl+C没有任何作用。 我们在终端窗口看到输入的内容,但后台任务并没有收到这些输入,它仍然在愉快地运行。

事实上,我们现在处于一种混合状态。 我们可以在终端窗口中输入,但输入的内容会被ping命令的滚动输出迅速覆盖。我们输入的任何内容都会在前台生效。

为了停止我们的后台任务,我们需要把它带到前台,然后再停止它。

fg 命令的用法

fg命令可以将后台任务带到前台。 和bg命令一样,它也可以带或不带作业号使用。 带作业编号使用意味着它将操作指定的作业。 如果不带作业编号使用,则会使用最近被送到后台的命令。

如果我们输入fg,我们的ping命令将被带到前台。 我们输入的字符会与ping命令的输出混杂在一起,但它们由shell处理,就像它们是在命令行中正常输入的一样。 事实上,从Bash shell的角度来看,事情就是这样发生的。

fg

现在ping命令又在前台运行了,我们可以使用Ctrl+C来终止它。

Ctrl+C

理解信号机制

这个过程可能有点混乱。 显然,当进程不产生输出且不需要输入时,在后台运行进程效果最好。

无论如何,我们的例子展示了如何:

将进程置于后台。
在后台恢复进程运行。
将进程返回到前台。
终止进程。

当你使用Ctrl+CCtrl+Z时,你实际上是在向进程发送信号。 这些是使用kill命令的快捷方式。 有64种不同的信号可以被kill命令发送。 你可以在命令行中使用kill -l列出它们。 kill不是这些信号的唯一来源。 系统内部的其他进程也会自动触发其中的一些信号。

下面是一些常用的信号:

SIGHUP:信号1。当正在运行的终端关闭时,会自动发送给进程。
SIGINT:信号2。发送给按下Ctrl+C的进程。 该进程会被中断,并被告知终止。
SIGQUIT:信号3。如果用户发送退出信号(通常是Ctrl+D),则发送给进程。
SIGKILL:信号9。进程会被立即杀死,不会尝试进行干净的关闭。 进程不会优雅地结束。
SIGTERM:信号15。这是kill命令发送的默认信号。 这是标准的程序终止信号。
SIGTSTP:信号20。当你使用Ctrl+Z时发送给进程。 它会停止进程并将其置于后台。

对于没有快捷键组合的信号,我们必须使用kill命令来发送。

进一步的作业控制

使用Ctrl+Z移动到后台的进程会处于停止状态。 我们必须使用bg命令重新启动它。 将程序作为正在运行的后台进程启动很简单。 只需在命令行末尾添加一个&符号即可。

虽然后台进程最好不向终端窗口写入输出,但我们将使用一个反例。我们需要在屏幕截图中展示一些可以参考的内容。以下命令将在后台进程中启动一个无限循环:

while true; do echo "How-To Geek Loop Process"; sleep 3; done &

系统会通知我们进程的作业编号和进程ID。 我们的作业编号是1,进程ID是1979。我们可以使用这些标识符来控制进程。

我们无限循环的输出开始出现在终端窗口中。 和之前一样,我们可以使用命令行,但是我们发出的任何命令都会散布在循环进程的输出中。

ls

要停止我们的进程,我们可以使用jobs命令查看作业编号,然后使用kill命令。

jobs命令会报告我们的进程是作业编号1。要将此编号与kill命令一起使用,我们必须在其前面加上百分号%

jobs
kill %1

kill命令会向进程发送SIGTERM信号(信号编号15)并终止它。 下次按下回车键时,将显示作业状态。它会把进程列为“已终止”。如果进程没有响应kill命令,你可以尝试更强硬的方式。 将killSIGKILL信号一起使用,信号编号为9。只需将数字9放在kill命令和作业编号之间。

kill 9 %1

内容总结

Ctrl+C:向进程发送SIGINT信号2(如果它正在接受输入),并告知其终止。
Ctrl+D:向进程发送SIGQUIT信号3(如果它正在接受输入),并告知其退出。
Ctrl+Z:向进程发送SIGTSTP信号20,并告知其停止(挂起)并变为后台进程。
jobs:列出后台作业并显示其作业编号。
bg job_number:重新启动后台进程。 如果不提供作业编号,则会使用最近被送入后台的进程。
fg job_number:将后台进程带到前台并重新启动它。 如果不提供作业编号,则会使用最近被送入后台的进程。
命令行&:在命令行末尾添加&符号会将该命令作为正在运行的后台任务执行。
kill %job_number:向进程发送SIGTERM信号15以终止它。
kill 9 %job_number:向进程发送SIGKILL信号9,并立即终止它。