7. 作业控制
作业控制(Job Control)是一种允许用户通过一个终端同时管理多个进程(作业)的机制。
它使得用户可以在一个终端会话中启动、停止、恢复、前后台切换以及终止作业,从而实现对多任务的灵活控制。
本节课我们要学习如下四个命令:
- jobs 命令
- fg 命令
- bg 命令
- nohup 命令
以上命令是作业控制(Job Control)相关的命令,主要用于管理进程的前后台运行、终端断开后的持久化以及作业查询等功能。
作业控制中的几个概念。
- 作业(Job):
- 一个作业通常对应一个进程或多个进程通过管道(
|)连接的进程组。(管道我们后面再讲)
- 一个作业通常对应一个进程或多个进程通过管道(
- 前台作业(Foreground Job):
- 前台作业:独占终端输入/输出,用户需等待其完成才能输入新命令。
- 后台作业(Background Job):
- 在后台运行,不占用终端,用户可继续输入其他命令。
细心的学者会发现,当我们打开一个终端并在终端运行程序时,如果终端关闭了,那么在终端中使用命令启动的进程也停止的运行。为什么呢?终端又有什么作用呢?
先来观察一个现象,我们在终端中运行 python3 always_run.py,如下:
weimingze@mzstudio:~$ python3 always_run.py
PID: 4359
在打来另外一个终端,运行 ps -ejH --forest 查看进程树。我们看到如下:
weimingze@mzstudio:~$ ps -ejH --forest
PID PGID SID TTY TIME CMD
2 0 0 ? 00:00:00 kthreadd
...
4 0 0 ? 00:00:00 \_ kworker/R-rcu_gp
3485 2650 2650 ? 00:00:00 \_ gnome-terminal
3486 2650 2650 ? 00:00:00 | \_ gnome-terminal.
3492 3492 3492 ? 00:00:02 \_ gnome-terminal-
3502 3502 3502 pts/1 00:00:00 \_ bash
4359 4359 3502 pts/1 00:03:38 \_ python3
我们会发现 终端进程(gnome-terminal) 启动了 bash 进程,bash 进程启动了 python3 always_run.py 进程。
当终端(gnome-terminal)关闭,则终端进程会向所有的子进程发送 SIGHUP(值为 1) 的信号,子进程收到此信号会执行相应的操作(包括向他的子进程发送SIGHUP信号)然后退出运行。这就是为什么子进程会关闭的原因。
那 bash 又是什么呢?
bash 是命令的解释执行的程序。bash 接管我们键盘输入,解释执行我们输入的信息并启动进程,并能够让我们能看进程输出的结果。(后面章节会系统全面的讲解 bash)。
如下面这一行提示符就是 bash 的输出。
weimingze@mzstudio:~$
任何一个程序都有不变的三个通道:
- 标准输入:bash 接管键盘,由 bash 交给前台进程,作为前台进程的输入。
- 标准输出:由程序产生的正确信息,交给 bash(默认 bash 会打印在屏幕上)。
- 标准错误输出:由程序产生的错误提示信息,交给 bash(默认 bash 会打印在屏幕上)
前面讲述了基础知识,下面我们来讲解作业控制。
启动前台作业
在终端中正常是启动程序是启动前台作业。前台作业会独占标准输入。如:
weimingze@mzstudio:~$ python3 always_run.py
使用 Control + c 向前台作业发送 SIGINT 信号可以终止前台作业。
启动后台作业
在终端中 输入启动程序的命令后,在后面加一个 & 符号则是启动后台作业。后台作业不占标准输入,但默认的标准输出和标准错误输出依旧会在终端显示。如:
weimingze@mzstudio:~$ python3 always_run.py &
[1] 4484
weimingze@mzstudio:~$ PID: 4484
weimingze@mzstudio:~$
再次回车后会显示终端提示符 weimingze@mzstudio:~$ 此时你可以继续输入命令,刚运行的命令启动的进程就是后台作业。此时你的输入是交给 bash,而不是 always_run.py 这个程序。
在运行几次 python3 always_run.py &。
查看作业列表
jobs 命令
使用 jobs 命令可以查看当前 bash 启动的所有作业。如:
weimingze@mzstudio:~$ jobs
[1] Running python3 always_run.py &
[2]- Running python3 always_run.py &
[3]+ Running python3 always_run.py &
weimingze@mzstudio:~$ jobs -l
[1] 4484 Running python3 always_run.py &
[2]- 4486 Running python3 always_run.py &
[3]+ 4487 Running python3 always_run.py &
weimingze@mzstudio:~$
-l 选项会增加 PID 的显示列。
- 第一列:[1], [2], [3] 是 作业的编号。
- 第二列:PID
- 第三列:作业状态,Running:运行中、Stopped:已停止、Terminated:已终止。
- 第四列:命令行。
停止作业
在前台作业输入 Control + z 快捷键将向进程发送 SIGTSTP 信号。当前进程进入 停止状态(T)并成为后台作业。如:
weimingze@mzstudio:~$ python3 always_run.py
PID: 4514
^Z
[4]+ Stopped python3 always_run.py
weimingze@mzstudio:~$ jobs -l
[1] 4484 Running python3 always_run.py &
[2] 4486 Running python3 always_run.py &
[3]- 4487 Running python3 always_run.py &
[4]+ 4514 Stopped python3 always_run.py
weimingze@mzstudio:~$
恢复作业
使用 fg 命令 和 bg 命令 可以将进程修改为前台作业或后台作业。
fg命令
将后台作业变为前台作业,如果作业处于 停止状态 则发送 SIGCONT 信号使其进入运行状态。
命令格式
bg %<作业编号>
如
weimingze@mzstudio:~$ fg %4
python3 always_run.py
PID 为 4514 的进程变成前台作业。再次 Control + z 让其变为后台作业。
bg命令
将作业变为后台作业,向处于 停止状态 的作业发送 SIGCONT 信号使其进入运行状态。
命令格式
fg %<作业编号>
如
weimingze@mzstudio:~$ jobs -l
[1] 4484 Running python3 always_run.py &
[2] 4486 Running python3 always_run.py &
[3]- 4487 Running python3 always_run.py &
[4]+ 4514 Stopped python3 always_run.py
weimingze@mzstudio:~$ bg %4
[4]+ python3 always_run.py &
weimingze@mzstudio:~$ jobs -l
[1] 4484 Running python3 always_run.py &
[2] 4486 Running python3 always_run.py &
[3]- 4487 Running python3 always_run.py &
[4]+ 4514 Running python3 always_run.py &
weimingze@mzstudio:~$
PID 为 4514 的进程依旧是后台作业。但他处于运行状态了。
强制终止作业
使用 kill %<作业编号> 可以向一个后台作业发送 信号 让其退出。如:
weimingze@mzstudio:~$ kill %4
weimingze@mzstudio:~$ jobs -l
[1] 4484 Running python3 always_run.py &
[2] 4486 Running python3 always_run.py &
[3]- 4487 Running python3 always_run.py &
[4]+ 4514 Terminated python3 always_run.py
PID 为 4514 的进程已经终止了,使用 ps 命令也查不到此进程了。
nohup 命令
nohup 用于让 启动的进程 忽略挂断信号(SIGHUP)的操作,让启动的进程编程一个真正的后台进程,即使终端关闭,进程仍会继续执行。
命令格式
nohup 命令 [选项] &
&表示推向后台运行。
使用 nohup 执行的程序,他将忽略标准输入,同时将标准输出和标准错误输出不在指向终端,而是保存在文件 'nohup.out' 中。当然我们可以通过输出重定向将其指向其他文件或打印设备。
示例:
weimingze@mzstudio:~$ nohup python3 always_run.py &
[1] 4945
weimingze@mzstudio:~$ nohup: ignoring input and appending output to 'nohup.out'
weimingze@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
...
3399 3399 3399 ? 00:00:00 \_ gvfsd-metadata
3485 2650 2650 ? 00:00:00 \_ gnome-terminal
3486 2650 2650 ? 00:00:00 | \_ gnome-terminal.
3492 3492 3492 ? 00:00:02 \_ gnome-terminal-
3502 3502 3502 pts/1 00:00:00 \_ bash
4945 4945 3502 pts/1 00:01:06 \_ python3
weimingze@mzstudio:~$ jobs
[1]+ Running python3 always_run.py &
weimingze@mzstudio:~$
可见此时 PID 为 4945 的进程是 bash 进程的子进程。然后我们关闭终端。再查看进程。
weimingze@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
...
3399 3399 3399 ? 00:00:00 \_ gvfsd-metadata
4945 4945 3502 ? 00:05:27 \_ python3
此时 PID 为 4945 的进程依旧存在,但他的父进程已经不在是 bash 进程,而是 systemd 进程了。
此时 PID 为 4945 的进程已经不在依赖任何终端,他变成了 守护进程。
守护进程
守护进程是指不依附于某个终端而独立运行的程序。他是一个后台作业,通常提供某种服务,比如 网站前端服务 nginx、ssh 远程连接、Django 或 Flask 后端服务程序等。
练习:
- 使用
python3 always_run.py &运行程序将其推向后台。 - 查询后台任务。
- 将后台任务恢复到前台。
- 使用
Control + c终止这个程序。