7. 作业控制

作业控制(Job Control)是一种允许用户通过一个终端同时管理多个进程(作业)的机制。

它使得用户可以在一个终端会话中启动、停止、恢复、前后台切换以及终止作业,从而实现对多任务的灵活控制。

本节课我们要学习如下四个命令:

  1. jobs 命令
  2. fg 命令
  3. bg 命令
  4. nohup 命令

以上命令是作业控制(Job Control)相关的命令,主要用于管理进程的前后台运行、终端断开后的持久化以及作业查询等功能。

作业控制中的几个概念。

  1. 作业(Job):
    • 一个作业通常对应一个进程或多个进程通过管道(|)连接的进程组。(管道我们后面再讲)
  2. 前台作业(Foreground Job):
    • 前台作业:独占终端输入/输出,用户需等待其完成才能输入新命令。
  3. 后台作业(Background Job):
    • 在后台运行,不占用终端,用户可继续输入其他命令。

细心的学者会发现,当我们打开一个终端并在终端运行程序时,如果终端关闭了,那么在终端中使用命令启动的进程也停止的运行。为什么呢?终端又有什么作用呢?

先来观察一个现象,我们在终端中运行 python3 always_run.py,如下:

weimz@mzstudio:~$ python3 always_run.py
PID: 10377

在打来另外一个终端,运行 ps -ejH --forest 查看进程树。我们看到如下:

weimz@mzstudio:~$ ps -ejH --forest
    PID    PGID     SID TTY          TIME CMD
      2       0       0 ?        00:00:00 kthreadd
      3       0       0 ?        00:00:00  \_ pool_workqueue_release
    ...
   2956    2956    2956 ?        00:05:51  \_ gnome-shell
   3445    2956    2956 ?        00:00:01  |   \_ Xwayland
   3784    2956    2956 ?        00:00:00  |   \_ mutter-x11-fram
   3886    2956    2956 ?        00:00:04  |   \_ gjs
  10042    2956    2956 ?        00:00:39  |       \_ ptyxis
  10054    2956    2956 ?        00:00:00  |           \_ xdg-terminal-ex
  10062   10062   10062 ?        00:00:00  |           \_ ptyxis-agent
  10087   10087   10087 pts/0    00:00:00  |               \_ bash
  10389   10389   10087 pts/0    00:00:00  |               |   \_ ps
  10171   10171   10171 pts/1    00:00:00  |               \_ bash
  10377   10377   10171 pts/1    00:00:25  |                   \_ python3
   ...

我们会发现 终端进程(gnome-terminal) 启动了 bash 进程,bash 进程启动了 python3 always_run.py 进程。

当终端(gnome-terminal)关闭,则终端进程会向所有的子进程发送 SIGHUP(值为 1) 的信号,子进程收到此信号会执行相应的操作(包括向他的子进程发送SIGHUP信号)然后退出运行。这就是为什么子进程会关闭的原因。

bash 又是什么呢?

bash 是命令的解释执行的程序。bash 接管我们键盘输入,解释执行我们输入的信息并启动进程,并能够让我们能看进程输出的结果。(后面章节会系统全面的讲解 bash)。

如下面这一行提示符就是 bash 的输出。

weimz@mzstudio:~$

任何一个程序都有不变的三个通道:

  1. 标准输入:bash 接管键盘,由 bash 交给前台进程,作为前台进程的输入。
  2. 标准输出:由程序产生的正确信息,交给 bash(默认 bash 会打印在屏幕上)。
  3. 标准错误输出:由程序产生的错误提示信息,交给 bash(默认 bash 会打印在屏幕上)

前面讲述了基础知识,下面我们来讲解作业控制。

启动前台作业

在终端中正常是启动程序是启动前台作业。前台作业会独占标准输入。如:

weimz@mzstudio:~$ python3 always_run.py

使用 Control + c 向前台作业发送 SIGINT 信号可以终止前台作业。

启动后台作业

在终端中 输入启动程序的命令后,在后面加一个 & 符号则是启动后台作业。后台作业不占标准输入,但默认的标准输出和标准错误输出依旧会在终端显示。如:

weimz@mzstudio:~$ python3 always_run.py &
[1] 10403
weimz@mzstudio:~$ PID: 10403

再次回车后会显示终端提示符 weimz@mzstudio:~$ 此时你可以继续输入命令,刚运行的命令启动的进程就是后台作业。此时你的输入是交给 bash,而不是 always_run.py 这个程序。

在运行几次 python3 always_run.py &

查看作业列表

jobs 命令

使用 jobs 命令可以查看当前 bash 启动的所有作业。如:

weimz@mzstudio:~$ jobs
[1]   运行中                  python3 always_run.py &
[2]-  运行中                  python3 always_run.py &
[3]+  运行中                  python3 always_run.py &
weimz@mzstudio:~$ jobs -l
[1]  10403 运行中                  python3 always_run.py &
[2]- 10407 运行中                  python3 always_run.py &
[3]+ 10409 运行中                  python3 always_run.py &

-l 选项会增加 PID 的显示列。

停止作业

在前台作业输入 Control + z 快捷键将向进程发送 SIGTSTP 信号。当前进程进入 停止状态(T)并成为后台作业。如:

weimz@mzstudio:~$ python3 always_run.py
PID: 10418
^Z
[4]+  已停止                  python3 always_run.py
weimz@mzstudio:~$ jobs -l
[1]  10403 运行中                  python3 always_run.py &
[2]  10407 运行中                  python3 always_run.py &
[3]- 10409 运行中                  python3 always_run.py &
[4]+ 10418 已停止                  python3 always_run.py

恢复作业

使用 fg 命令bg 命令 可以将进程修改为前台作业或后台作业。

fg命令

将后台作业变为前台作业,如果作业处于 停止状态 则发送 SIGCONT 信号使其进入运行状态。

命令格式

fg %<作业编号>

weimz@mzstudio:~$ fg %4
python3 always_run.py

PID 为 10418 的进程变成前台作业。再次 Control + z 让其变为后台作业。

bg命令

将作业变为后台作业,向处于 停止状态 的作业发送 SIGCONT 信号使其进入运行状态。

命令格式

bg %<作业编号>

weimz@mzstudio:~$ jobs -l
[1]  10403 运行中                  python3 always_run.py &
[2]  10407 运行中                  python3 always_run.py &
[3]- 10409 运行中                  python3 always_run.py &
[4]+ 10418 已停止                  python3 always_run.py
weimz@mzstudio:~$ bg %4
[4]+ python3 always_run.py &
weimz@mzstudio:~$ jobs -l
[1]  10403 运行中                  python3 always_run.py &
[2]  10407 运行中                  python3 always_run.py &
[3]- 10409 运行中                  python3 always_run.py &
[4]+ 10418 运行中                  python3 always_run.py &
weimz@mzstudio:~$ 

PID 为 10418 的进程依旧是后台作业。但他处于运行状态了。

强制终止作业

使用 kill %<作业编号> 可以向一个后台作业发送 信号 让其退出。如:

weimz@mzstudio:~$ kill %4
[4]+  已终止                  python3 always_run.py
weimz@mzstudio:~$ jobs -l
[1]  10403 运行中                  python3 always_run.py &
[2]- 10407 运行中                  python3 always_run.py &
[3]+ 10409 运行中                  python3 always_run.py &

PID 为 10418 的进程已经终止了,使用 ps 命令也查不到此进程了。

nohup 命令

nohup 用于让 启动的进程 忽略挂断信号(SIGHUP)的操作,让启动的进程编程一个真正的后台进程,即使终端关闭,进程仍会继续执行。

命令格式

nohup 命令 [选项]  &

& 表示推向后台运行。

使用 nohup 执行的程序,他将忽略标准输入,同时将标准输出和标准错误输出不在指向终端,而是保存在文件 'nohup.out' 中。当然我们可以通过输出重定向将其指向其他文件或打印设备。

示例:

weimz@mzstudio:~$ nohup python3 always_run.py &
[1] 10476
weimz@mzstudio:~$ nohup: ignoring input and appending output to 'nohup.out'

weimz@mzstudio:~$ ps -ejH --forest
    PID    PGID     SID TTY          TIME CMD
      2       0       0 ?        00:00:00 kthreadd
      3       0       0 ?        00:00:00  \_ pool_workqueue_release
    ...
   2956    2956    2956 ?        00:06:18  \_ gnome-shell
   3445    2956    2956 ?        00:00:02  |   \_ Xwayland
   3784    2956    2956 ?        00:00:00  |   \_ mutter-x11-fram
   3886    2956    2956 ?        00:00:04  |   \_ gjs
  10042    2956    2956 ?        00:00:59  |       \_ ptyxis
  10054    2956    2956 ?        00:00:00  |           \_ xdg-terminal-ex
  10062   10062   10062 ?        00:00:00  |           \_ ptyxis-agent
  10087   10087   10087 pts/0    00:00:00  |               \_ bash
  10476   10476   10087 pts/0    00:00:12  |               |   \_ python3
  10479   10479   10087 pts/0    00:00:00  |               |   \_ ps
  10171   10171   10171 pts/1    00:00:00  |               \_ bash
  10377   10377   10171 pts/1    00:07:06  |                   \_ python3
   3013    3013    3013 ?        00:00:00  \_ at-spi-bus-laun
   ...
weimz@mzstudio:~$ jobs
[1]+  运行中                  nohup python3 always_run.py &
weimz@mzstudio:~$ 

此时 PID 为 10476 的进程依旧存在,但他的父进程已经不在是 bash 进程,而是 systemd 进程了。

此时 PID 为 10476 的进程已经不在依赖任何终端,他变成了 守护进程

守护进程

守护进程是指不依附于某个终端而独立运行的程序。他是一个后台作业,通常提供某种服务,比如 网站前端服务 nginx、ssh 远程连接、Django 或 Flask 后端服务程序等。

练习:

  1. 使用 python3 always_run.py & 运行程序将其推向后台。
  2. 查询后台任务。
  3. 将后台任务恢复到前台。
  4. 使用 Control + c 终止这个程序。