Linux深入探索15-进程和作业控制

----- 最近更新【2022-01-25】-----

本文目录结构预览:

  • 一、简介
  • 二、内核管理进程的方式
    1、调度器 与 CPU 时间
    2、系统调用
  • 三、父进程与子进程
    1、概念
    2、进程分叉到死亡
    3、第一个进程:init
  • 四、作业控制-介绍
    1、概念
    2、作业控制相关工具
    3、作业与进程的区别
  • 五、作业控制-操作
    1、在后台运行作业:&
    2、挂起作业:^Z
    3、挂起 shell:suspend
    4、显示作业列表:jobs
    5、将作业移至前台:fg
    6、将作业移至后台:bg
  • 六、进程查找-ps
    1、ps 简介
    2、常用选项
    3、常见的数据列
    4、尝试追踪一个进程的父进程 ID
    5、查找指定名称的进程
  • 七、进程管理
    1、监视系统进程:top
    2、显示进程树:pstree
    3、向进程发送信号:kill
  • 八、参考

一、简介

在 Unix 系统中,每个对象或者用文件表示,或者用进程表示。简单地讲,文件存放数据或者允许访问资源,而进程是正在执行的程序。

在Unix中,我们不谈论程序的执行,而是谈论进程(process)的执行。进程就是装载到内存中准备运行的程序,以及程序的数据与跟踪程序状态所需的信息。进程可以分成更小的单元,称为线程,线程是一组运行在进行环境中的指令。因此可以将程序的结构描述为,在一个进程之内,不止一个线程在同时运行。

所有的进程都由内核管理,内核是操作系统的核心部分。当进程创建时,内核赋予其中一个唯一的标识号,为个标识号称为进程 IDPID 。为了跟踪管理系统中的所有进程,内核维护了一个进程表。(在这点上,与文件的 i 节点号也挺相似的。)

二、内核管理进程的方式

1、调度器 与 CPU 时间

小型的 Unix 系统很容易“同时”运行 100 多个进程。在大型系统中,可能有数百个甚至数千个进程,这些进程都需要共享系统的资源:处理器、内存、I/O 设备、网络连接等。为了管理这样一个复杂的工作负荷,内核提供了一个复杂的调度服务,有时候称其为调度器(scheduler)。

调度器一直维护一个所有正在等待执行的进程列表。通过使用复杂的算法,调度器每次选择一个进程,给予这个进程在一个短暂的时间间隔(称为时间片)中运行的机会。

当讨论时间片概念时,我们通常将处理时间称为 CPU 时间

一个典型的时间片通常是 10 毫秒的 CUP 时间。一旦时间片用用尽,该进程将返回到调度列表,由内核启动另一个进程。通过这种方式,最终每个进程都将获得足够的 CPU 时间来完成它们的工作。

2、系统调用

内核是操作系统的核心,因此,内核会为进程提供一些基本的服务,具体包括:

  • 内存管理(虚拟内存管理,包括分页)
  • 进程管理(进程创建、终止、调度)
  • 进程间通信(本地、网络)
  • 输入/输出(通过设备驱动程序,即与物理设备实际通信的程序)
  • 文件管理
  • 安全和访问控制
  • 网络管理(如 TCP/IP)

当进程需要执行服务时,它就使用系统调用发送请求。例如,进程使用系统调用初始化 I/O 操作。

许多重要的任务只能由内核执行。当进程需要执行这样的任务时,进程必须使用系统调用,向内核发送一个执行任务的请求。Unix/Linux 系统通常有200-300个不同的系统系统调用。最常用的系统调用是那些用于进程控制(fork、wait、exec、exit和kill)和 文件 I/O(open、read、write 和 close)的系统调用。

常用的系统调用:

系统调用 目的说明
fork 创建当前进程的一个副本
wait 等待另一个进程结束执行
exec 在当前进程中执行一个新的程序
exit 终止当前进程
kill 向另一个进程发送一个信号
open 打开一个用于读取或写入的文件
read 从文件中读取数据
write 向文件中写入数据
close 关闭文件

三、父进程与子进程

1、概念

除了系统的初始化进程(系统中所有进程的父进程)外,Unix 中的每个进程都是由另一个进程创建。

fork 系统调用创建当前进程的一个副本。一旦发生这种情况,我们称原始进程为父进程(parent process)。新进程是和父进程一模一样的副本进程,称为子进程(child process)。

就以我们最熟悉的 shell 来说,shell 是一个充当用户界面和脚本解释器的程序。但是,一旦 shell 开始运行,它只是众多系统进程中的一个。与所有进程一样,shell 也拥有自己的进程 ID(PID),用如下方法可以方便地查看当前 shell 的 PID:

[nosee@instance-4 ~]$ ps
  PID TTY          TIME CMD
10548 pts/0    00:00:00 bash
11440 pts/0    00:00:00 ps
[nosee@instance-4 ~]$ echo $$;
10548

当在 shell 中输入命令时,我们把命令分为两种类型:内部命令(内置命令)和外部命令。内部命令直接由 shell 解释,因此不需要创建新进程。但是,每当运行外部命令时,shell 必须创建一个新进程。

2、进程分叉到死亡

为了创建一个新进程,shell 所做的第一件事就是使用 fork 系统调用创建一个全新的进程。原始进程成为父进程,而新进程就成为子进程。

一旦进程分叉(创建)成功,就发生两件事情:
首先,子进程使用 exec 系统调用将它自身从运行 shell 的进程变成运行外部程序的进程。
其次,父进程使用 wait 系统调用暂停,直到子进程结束执行。

最终,外部程序结束,此时子进程使用 exit 系统调用停止自身。

每当进程永久停止时,不管是由于什么原因停止,我们都称进程死亡(die)或者终止(terminate)。每当进程死亡时,进程的所有资源(内存、文件等)都将被释放,从而可以被其他进程使用。此时称杀死的进程为僵进程(zombie)。僵进程已经不再是一个真正的进程,但是它仍然在进程表中保留着自己的条目。这是因为该条目包含最近死亡的子进程的数据,而父进程可能会需要这些数据。

在子进程成为僵进程后,父进程(一直在等待子进程死亡)立即被内核唤醒。现在父进程有机会查看进程表中的僵进程条目,看看发生了什么结果。然后内核将进程表中的僵进程条目移除。

3、第一个进程:init

当我们追踪一个进程的生成过程,如果追踪得足够远,那么可以得到一个结论,即在某个地方,必定会有第一个进程。每个 Unix 系统都拥有一个进程(至少间接拥有)是系统中其它所有进程的父进程。

理解 Unix 的第一个进程,需要从系统的启动开始说起。这里先理解一个思想:引导。

引导的思想就是一个困难的、复杂的目标可以通过一个小的动作开始,然后以这个小动作为基础,一步一步地到达期望目标而完成。计算机系统就是以这种方式启动的:当打开计算机的电源时,一个单独的、小型的程序自动运行。这个程序启动另一个程序,一个更复杂的程序,然后逐步递进。最终,操作系统(一个非常复杂的程序)接过控制,完成初始化。

在引导过程的末尾,内核“手动”创建一个特殊的进程,而不是通过分叉。这个进程的 PID为 0。我们可以将它称为空闲进程。在执行一些重要的功能之后(如初始化内核所需的数据结构),空闲进程进行分叉,创建 #1 号进程。然后空闲进程执行一个非常简单的程序,该程序实质上是一个无穷的循环,不做任何事(即进入永久的休眠,这也是它命名的原因)。这里的思想就是,每当没有进程等待执行时,调度器就运行空闲进程。实际上,如果使用 ps 命令显示进程 #0 的状态时,内核会否认该进程的存在。

接下来就是进程 #1 了,进程 #1 执行设置内核及结束引导过程所需的剩余步骤,所以我们称它为初始化进程(init process),并将该进程的实际程序命名为 init 。具体而言,初始化进程打开系统控制台,挂载根文件系统,然后运行包含在文件 /etc/inittab(不同系统可能不一样,如 Debian 类系统是 /etc/init.d)中的 shell 脚本。这个过程中,init 多次分叉,创建运行系统所需的基本进程,并允许用户登录。在这一过程中,init 成为系统中所有其他进程的祖先。init 进程永远不会停止,直到系统关闭。

大多数的第三代进程都是守护进程,守护进程的任务就是等待某些事情的发生,然后进行适当的响应。例如,有一些守护进程不做其它事,专门等待用户登录。当用户准备登录时守护进程就创建另一个进程来处理这一任务。然后登录守护进程创建另一个进程运行用户有 shell。最后,每当 shell 需要执行用户的程序时,shell 就会妴另一个进程来执行任务。

注:在 Debian 系统中,初始化进程实际程序命名为 systemd,而不是 init。

四、作业控制-介绍

1、概念

当我们需要在后台运行一个进程时,可以在命令的末尾键入一个&字符以 异步进程(asynchronous process)运行该程序。异步进程有两个明确的特征:第一,默认情况下,标准输入与空文件 /dev/null 相连。第二,因为进程自己运行,不需要用户输入,所以该进程不响应 intr 和 quit 信号。所以,如果异步进程遇到了问题,那么也不能使用^C键或者^\键来终止进程,只能使用 kill 命令。

为了能更加方便的管理后台进程,很多 shell 都添加了一个新功能,即 作业控制

作业控制是一项由 shell 支持的特性,允许同时运行多项作业,其中一个作业在前台运行,其他作业在后台运行。作业可以有选择地挂起(暂停)、重新启动、在前台和后台之间切换,以及显示状态。

作业控制的本质就是将每条输入的命令视为一个作业,该作业由唯一的 作业号(也称 作业 ID)来标识。

2、作业控制相关工具

为了控制和管理作业,可以将作业 ID 和一系列命令、变量、终端设置、shell 变量 以及 shell 选项 一起使用。如下表,列举了这些工具:

1)作业控制命令

命令 说明
jobs 显示作业列表
ps 显示进程列表
fg 将作业移至前台
bg 将作业移至后台
suspend 挂起当前 shell
^Z 挂起当前前台作业
kill 向作业发送信号;默认情况下,终于作业

2)变量

变量 说明
echo $$ 显示当前 shell 的 PID
echo $! 显示上一条移至后台的命令的 PID

3)终端设置

终端设置 说明
stty tostop 挂起试图向终端写数据的后台作业
stty -tostop 关闭 tostop

4)shell 选项(bash shell)

shell 选项 说明
set -o monitor 允许作业控制
set +o monitor 关闭 monitor
set -o notify 当后台作业结束时立即通知
set +o notify 关闭 notify

3、作业与进程的区别

在很大程度上,这两个概念是相似的,人们通常交换着使用术语“作业”和“进程”。

但严格地讲,它们之间还是区别的。

  • 进程是正在执行或者准备执行的程序,则作业是指解释整个命令所需的全部进程。
  • 进程由内核控制,而作业由 shell 控制。
  • 内核使用进程表记录进程;而 shell 也采用相同的方式,使用作业表记录作业。

例如,当输出一个简单的命令date。该命令生成一个单独的进程(该进程拥有自己的进程 ID),和一个单独的作业(该作业拥有自己的作业 ID)。在作业运行时,进程表会有一个新的条目,作业表也会有一个新的条目。

假如,是复杂一点的命令,如管道命令:who | cut -c 1-8 | sort | uniq -c。这条命令会生成 4 个不同的进程,每个进程都有自己的进程 ID。但是,整个管道线被认为是一个单独的作业,拥有一个单独的作业 ID 。该作业运行时,进程表中有 4 个条目,但是作业表中只有一个条目。

五、作业控制-操作

1、在后台运行作业:&

为了在后台运行作业,需要在命令末键入一个&字符。

如:

[nosee@instance-4 ~]$ ls > aaa &
[1] 14836
[nosee@instance-4 ~]$

每次在后台运行作业时,shell 都会显示作业的作业 ID 和进程 ID。shell 从 1 开始为作业分配作业 ID。而进程 ID 由内核分配,大多时候是多位的数字。

当后台作业结束时,shell 会给你发送一个简短的状态消息。为了防止状态消息干扰另一个程序的输出,shell 不会立即通知你。shell 会一直等待,直到要显示下一个 shell 提示,才给你发送通知。

如上面是一个简单的例子,现在肯定早就结束了,那我可以按回车键刷新一下 shell 提示就可以收到通知。

[nosee@instance-4 ~]$ ls > aaa &
[1] 14991
[nosee@instance-4 ~]$ 
[1]+  Done                    ls --color=auto > aaa
[nosee@instance-4 ~]$ 

或者我输入一条新命令的时候,shell 就会在我新命令的输出最后面通知我(也就是下一个 shell 提示之前),如下:

[nosee@instance-4 ~]$ ls > aaa &
[1] 14996
[nosee@instance-4 ~]$ pwd
/home/nosee
[1]+  Done                    ls --color=auto > aaa
[nosee@instance-4 ~]$ 

如果不希望等待,可以使用命令set -o notify来打开即时通知。但是即时通知可能会干扰到你正在运行的其他命令的输出。

2、挂起作业:^Z

在任何时候,每个作业都属于 3 种状态中的一种:前台运行、后台运行、暂停。

当需要暂停前台作业时,可以按^Z键(Ctrl-Z)。这时发送 susp 信号,从而使进程暂停。当通过这种方式暂停进程时,我们称进程 挂起(suspend),或者将进程 停止(stop)。这里的停止是指临时终止,一个停止的作业是还可以重新启动的。

如果希望永久的停止程序,则必须按^C键或者使用 kill 命令。

当希望恢复挂起的程序时,可以使用 fg 命令将该程序移回前台。

例如,当你使用vim aaa命令在编辑文件时,突然想出去查看一下日期。这时你可以按下^Z键回到 shell 界面,执行你想执行的命令date,然后你再使用fg命令回到你的 vim 编辑界面。

[nosee@instance-4 ~]$ vim aaa 

[1]+  Stopped                 vim aaa
[nosee@instance-4 ~]$ date
Sun Jan 23 21:06:15 UTC 2022
[nosee@instance-4 ~]$ fg
vim aaa

上面的Stopped的意思就是该作业已经被挂起。当挂起作业时,如果你不做任何处理,进程会无限期的在等待。

3、挂起 shell:suspend

按下^Z键将挂起在前台运行的任何作业。但是有一个进程不会挂起,这个进程就是当前 shell。如果希望挂起当前 shell,则需要使用suspend命令。(suspend - Suspend shell execution.)

例:

[nosee@instance-4 ~]$ su
Password: 
root@instance-4:/home/nosee# 
root@instance-4:/home/nosee# suspend 

[2]+  Stopped                 su
[nosee@instance-4 ~]$ 
[nosee@instance-4 ~]$ fg
su
root@instance-4:/home/nosee# 

默认情况下,是不允许挂起登录 shell 的。如你在当前 shell 以su -命令启动超级用户 shell 时,将创建一个登录 shell。如果这种希望希望挂起这个新 shell ,则必须使用命令 suspend -f,带-f选项表示强制挂起。如:

[nosee@instance-4 ~]$ su - 
Password: 
root@instance-4:~# suspend 
-bash: suspend: cannot suspend a login shell
root@instance-4:~# suspend -f

[3]+  Stopped                 su -
[nosee@instance-4 ~]$ 
[nosee@instance-4 ~]$ fg
su -
root@instance-4:~# 

注:命令su启动的是一个子 shell,而命令su -是打开一个新的登录 shell。

4、显示作业列表:jobs

在任何时候都可以使用 jobs 命令显示所有作业的列表。(Display status of jobs.)

jobs 是一个 shell 的内置命令,可以使用help jobs去查看它的使用说明。

例:

[nosee@instance-4 ~]$ jobs
[1]   Stopped                 vim aaa
[2]-  Stopped                 php -a
[3]+  Stopped                 vim letter.txt
  • 其中第一列的123为作业的作业 ID,后面的+表示“当前作业”,-表示“前一个作业”。
  • 第二列中的Stopped表示这是挂起的作业,如果是Running则表示后台运行中的作业。
  • 最后一列是该作业的命令.

如果希望同时查看对应的进程 ID,则需要使用选项-l。如:

[nosee@instance-4 ~]$ jobs -l
[1]  15045 Stopped                 vim aaa
[2]- 15464 Stopped (tty input)     php -a
[3]+ 15465 Stopped                 vim letter.txt

5、将作业移至前台:fg

fg - 将作业移至前台。(Move job to the foreground)

语法:fg [job_spec]

fg 的语法非常灵活,不带任何参数的话,则会作用于当前作业,那作业 ID 后面有一个+的那个。

将当前作业移动至前台可以使用命令:fgfg +fg %+%。这4个命令都是一样的效果。

[nosee@instance-4 ~]$ jobs -l
[1]  15045 Stopped                 vim aaa
[2]- 15464 Stopped (tty input)     php -a
[3]+ 15465 Stopped                 vim letter.txt

例如,上面的3个作业中,如果我想把作业 ID 为2的作业移至前台,那我可以使用下列命令中的任何一条都可以:

%2
fg 2
fg -
fg %2
fg %-
fg %?php
fg %?-a

注:
fg命令是 shell 命令中唯一条命令名本身可选的命令。
即只输入命令的参数就行,可以不输入命令本身,如%2就相当于fg %2

6、将作业移至后台:bg

bg - 将作业移至后台。(Move jobs to the background.)

在使用命令bg指定作业时,需要遵循和fg命令相同的规则。

命令bg的使用频率要比命令fg少得多。但是有一种情况bg命令挺实用的:
就是当准备在后台运行时,但是忘记了在后面输入&。这时只需按下^Z键挂起作业,然后用bg命令将作业移至后台。

六、进程查找-ps

1、ps 简介

ps 是一个功能强大的工具,可以帮助查找特定的 PID(进程 ID),检查进程正在做什么,并概述系统中正在发生的第一件事情。(report a snapshot of the current processes)

ps 拥有许多使人迷惑的选项,这种情况主要是由于历史的原因造成的。在很久以前,Unix 有两个主要的分支:UNIX 和 BSD,它们都有自己的 ps 版本,每个版本都有自己的选项。随着时间的推移,两种类型的 ps 都很出名,并且被广泛使用。

最终的结果是,许多现代版本的 ps 同时支持两种类型的选项,即所谓的 Unix 选项和 BSD 选项。

查看 ps 的 man 手册如下,发现还有一种 GNU 系统的选项,但是基本上用不着,所以这里就不讨论了。

This version of ps accepts several kinds of options:
       1   UNIX options, which may be grouped and must be preceded by a dash.
       2   BSD options, which may be grouped and must not be used with a dash.
       3   GNU long options, which are preceded by two dashes.

对于 ps 的选项来说,有一个有趣的传统:Unix 选项通常以连字符-开头,而 BSD 选项前面没有连字符。

ps 命令较简单的语法格式为:
使用 Unix 选项:ps [-aefFly] [-p pid] [-u userid]
使用 BSD 选项:ps [ajluvx] [p pid] [U userid]

默认情况下,ps 不会显示很多进程信息,只是列出与当前终端会话相关的进程。

[nosee@instance-4 ~]$ ps
  PID TTY          TIME CMD
10548 pts/0    00:00:00 bash
15464 pts/0    00:00:00 php
15757 pts/0    00:00:00 vim
20359 pts/0    00:00:00 ps

尽管 ps 拥有许多的选项,但是日常工作仅需要少数几个选项。

2、常用选项

1)Unix 选项

显示哪些进程: ——
ps 与 你的用户标识和终端 相关的进程
ps -a 与 任何用户标识和终端 相关的进程(except session leaders)
ps -e 所有进程(包括守护进程)
ps -p pid 与指定进程 ID pid 相关的进程
ps -u userid 与打指定用户标识 userid 相关的进程
显示哪些数据列: ——
ps PID TTY TIME CMD
ps -f UID PID PPID C STIME TTY TIME CMD
ps -F UID PID PPID C SZ RSS PSR STIME TTY TIME CMD
ps -l F S UID PID PPID C PRI NI ADDR SZ WCHSN TTY TIME CMD
ps -ly S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD
有用的特殊组合: ——
ps 显示自己的进程
ps -ef 显示所有用户进程,完整输出
ps -a 显示所有非守护进程的进程
ps -t - 仅显示所有守护进程

例:查看系统上的所有进程

To see every process on the system using standard syntax:
    ps -e
    ps -ef
    ps -eF
    ps -ely

例:显示进程树(选项:-H

To print a process tree:
    ps -ejH

2)BSD 选项

显示哪些进程: ——
ps 与你的用户标识和终端相关的进程
ps a 与任何用户标识和终端相关的进程
ps e 所有进程(包括守护进程)
ps p pid 与指定进程 ID pid 相关的进程
ps U userid 与打指定用户标识 userid 相关的进程
显示哪些数据列: ——
ps PID TTY TIME CMD
ps j PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ps v PID TTY STAT TIME MAJFL TRS DRS RSS %MEM COMMAND
有用的特殊组合: ——
ps 显示自己的进程
ps ax 显示所有进程
ps aux 显示所有进程,完整输出

例:查看系统上的所有进程

To see every process on the system using BSD syntax:
    ps ax
    ps axu

例:显示进程树(选项:f

To print a process tree:
    ps axjf

3、常见的数据列

1)Unix 选项

列标题 含义
ADDR 进程列中的虚拟地址
CMD 正被执行的命令名称
F 与进程相关的标志
NI nice 值,用于设置优先级
PID 进程 ID
PPID 父进程 ID
PRI 优先级(较大的数字=较低的优先级)
RSS 内存驻留空间大小(内存和管理)
S 状态代码:D(不可中断睡眠)、R(正在运行可可运行)、S(可中断睡眠)、T(挂起)、Z(僵进程)
STIME 累积系统时间
SZ 物理页的大小(内存管理)
TIME 累积CPU时间
TTY 控制终端的名称
UID 用户 ID
WCHAN 等待通道

2)BSD 选项

列标题 含义
%CPU CPU(处理器)使用百分比
%MEM 真实内存使用百分比
CMD 正被执行的命令的名称
COMMAND 正被执行的命令的完整名称
NI nice 值,用于设置优先级
PGID 进程组号
PID 进程 ID
PPID 父进程 ID
PRI 调度优先级
RSS 内存驻留空间大小(内存管理)
STAT 状态代码:O(正在运行)、R(运行)、S(正在睡眠)、T(挂起)、Z(僵进程)
TIME 累积 CPU 时间
TTY 控制终端的名称
UID 用户 ID
USER 用户标识
VSZ 虚拟大小(单位:KB)
WCHAN 等待通道

4、尝试追踪一个进程的父进程 ID

[nosee@instance-4 ~]$ ps -aj
  PID  PGID   SID TTY          TIME CMD
15464 15464 10548 pts/0    00:00:00 php
15757 15757 10548 pts/0    00:00:00 vim
19054 19054 18901 pts/1    00:00:00 vim
19766 19766 10548 pts/0    00:00:00 ps
[nosee@instance-4 ~]$ ps -fp 15464
UID        PID  PPID  C STIME TTY          TIME CMD
nosee    15464 10548  0 Jan23 pts/0    00:00:00 php -a
[nosee@instance-4 ~]$ ps -fp 10548
UID        PID  PPID  C STIME TTY          TIME CMD
nosee    10548 10547  0 Jan23 pts/0    00:00:00 -bash
[nosee@instance-4 ~]$ ps -fp 10547
UID        PID  PPID  C STIME TTY          TIME CMD
nosee    10547 10529  0 Jan23 ?        00:00:00 sshd: nosee@pts/0
[nosee@instance-4 ~]$ ps -fp 10529
UID        PID  PPID  C STIME TTY          TIME CMD
root     10529   585  0 Jan23 ?        00:00:00 sshd: nosee [priv]
[nosee@instance-4 ~]$ ps -fp 585
UID        PID  PPID  C STIME TTY          TIME CMD
root       585     1  0 Jan22 ?        00:00:01 /usr/sbin/sshd -D
[nosee@instance-4 ~]$ ps -fp 1
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Jan22 ?        00:00:06 /sbin/init

注:
1)如我们前面介绍的,追踪父进程最终总会回到初始化进程(init process)—— 所有进程的父进程。
2)TTY 列中的?(问号)表示该进程没有控制终端,我们称这样的进程为“守护进程”。

5、查找指定名称的进程

1) ps -ef | grep 名称

[nosee@instance-4 ~]$ ps -ef | grep php
root       412     1  0 21:11 ?        00:00:00 php-fpm: master process (/etc/php/7.3/fpm/php-fpm.conf)
www-data   522   412  0 21:11 ?        00:00:00 php-fpm: pool www
www-data   523   412  0 21:11 ?        00:00:00 php-fpm: pool www
nosee     1635  1237  0 21:41 pts/0    00:00:00 grep --color php
[nosee@instance-4 ~]$ ps -ef | grep mysql
mysql      515     1  0 21:11 ?        00:00:03 /usr/sbin/mysqld
nosee     1641  1237  0 21:41 pts/0    00:00:00 grep --color mysql
[nosee@instance-4 ~]$ ps -ef | grep nginx
root       449     1  0 21:11 ?        00:00:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data   451   449  0 21:11 ?        00:00:00 nginx: worker process
www-data   452   449  0 21:11 ?        00:00:00 nginx: worker process
nosee     1697  1237  0 21:46 pts/0    00:00:00 grep --color nginx

2) ps aux | grep 名称

[nosee@instance-4 ~]$ ps axu | grep php
root       412  0.0  0.5 196232 21428 ?        Ss   21:11   0:00 php-fpm: master process (/etc/php/7.3/fpm/php-fpm.conf)
www-data   522  0.0  0.1 196232  7568 ?        S    21:11   0:00 php-fpm: pool www
www-data   523  0.0  0.1 196232  7568 ?        S    21:11   0:00 php-fpm: pool www
nosee     1621  0.0  0.0   4836   820 pts/0    R+   21:40   0:00 grep --color php
[nosee@instance-4 ~]$ ps axu | grep mysql
mysql      515  0.1  9.8 1738588 398980 ?      Ssl  21:11   0:03 /usr/sbin/mysqld
nosee     1623  0.0  0.0   4836   884 pts/0    S+   21:41   0:00 grep --color mysql
[nosee@instance-4 ~]$ ps aux | grep nginx
root       449  0.0  0.0  65660  1728 ?        Ss   21:11   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data   451  0.0  0.0  66008  3440 ?        S    21:11   0:00 nginx: worker process
www-data   452  0.0  0.0  66008  3440 ?        S    21:11   0:00 nginx: worker process
nosee     1705  0.0  0.0   4836   824 pts/0    R+   21:46   0:00 grep --color nginx

3)如果想要同时显示出列的名称,可以这样:

[nosee@instance-4 ~]$ ps -ef | grep -E 'php|UID'
UID        PID  PPID  C STIME TTY          TIME CMD
root       412     1  0 21:11 ?        00:00:00 php-fpm: master process (/etc/php/7.3/fpm/php-fpm.conf)
www-data   522   412  0 21:11 ?        00:00:00 php-fpm: pool www
www-data   523   412  0 21:11 ?        00:00:00 php-fpm: pool www
nosee     1949  1237  0 21:56 pts/0    00:00:00 grep --color -E php|UID

4)用pgrep根据进程名查找相关进程信息

  • 先用pgrep [str] 命令进行模糊匹配,找到匹配该特征串的进程ID;
  • 其次根据进程ID显示指定的进程信息,ps --pid [pid];

如查询mysql的进程信息:

root@Chan:~# pgrep mysql
914
root@Chan:~# ps -p 914
  PID TTY          TIME CMD
  914 ?        00:01:32 mysqld    ## “CMD”栏则显示了线程名称
root@Chan:~# ps -u -p 914    ## 加上-u参数,可以显示进程详细信息
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
mysql      914  0.0 11.9 1107652 121424 ?      Ssl  Nov30   1:32 /usr/sbin/mysqld
root@Chan:~# ps -T -p 914    ## 在ps命令中,"-T"选项可以开启线程查看。
  PID  SPID TTY          TIME CMD
  914   914 ?        00:00:00 mysqld
  914  1094 ?        00:00:00 mysqld
  914  1099 ?        00:00:04 mysqld
  914  1100 ?        00:00:04 mysqld
...
  914  1136 ?        00:00:00 mysqld
  914  1137 ?        00:00:00 mysqld

七、进程管理

1、监视系统进程:top

为了查看指定的进程,可以使用 ps 命令。但是,ps 只显示进程的静态快照,即瞬间的进程状态。

进程是动态的,不断变化的。如果希望查看系统的整体运行情况,可以使用 top 程序每隔几秒钟显示整个系统的统计更新,并实时显示最重要的进程的信息。(display Linux processes)

top 程序用来显示系统上“顶端”进程的动态信息,也就是使用最多 CPU 时间的进程。

语法:top -hv|-bcEHiOSs1 -d secs -n max -u|U user -p pid -o fld -w [cols]

常用选项:

  • -d secs,刷新间隔,单位为秒。(默认3秒,不同系统可能会不一样)
  • -n max,刷新的总次数,单位:次。
  • -p pid,进程 ID,多个进程 ID 可以用逗号分开。

例:直接输入命令top

top - 05:19:43 up 1 day, 16:19,  2 users,  load average: 0.00, 0.07, 0.09
Tasks:  87 total,   1 running,  84 sleeping,   2 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3946.1 total,   2847.0 free,    523.4 used,    575.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   3147.3 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  408 root      20   0  196232  21696  16152 S   0.3   0.5   0:05.76 php-fpm7.3     
  506 mysql     20   0 1739756 397508  33584 S   0.3   9.8   3:07.49 mysqld 
20323 nosee     20   0    9928   3596   3016 R   0.3   0.1   0:00.32 top   
    1 root      20   0  169664  10252   7788 S   0.0   0.3   0:06.87 systemd       
    2 root      20   0       0      0      0 S   0.0   0.0   0:00.00 kthreadd
    3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp
    4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_par_gp      
    6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/0:0H-kblockd
    8 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 mm_percpu_wq 
    9 root      20   0       0      0      0 S   0.0   0.0   0:00.10 ksoftirqd/0 

1)top 信息说明:

  • 最前面的5行(系统整体的统计信息)
信息 说明
第一行 ——与uptime命令输出的结果相同——
top - 05:19:43 当前系统时间
up 1 day, 16:19 系统运行总时间(从开机到现在)
2 users 当前登录用户数量
load average: 0.00, 0.07, 0.09 CPU 的平均负载情况,三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值。
理想值:单核 CPU 情况(0.7), 多核 CPU 情况(CPU 核数 * 0.7)
一般,当系统负载 > CPU 核数 * 0.7 时,就需要进行一定的优化处理了
第二行:Tasks ——总结了进程数目和各种进程状态——
total、running、sleeping、
stopped、zombie
进程总数、正在运行的进程数、睡眠的进程数、停止的进程数、僵尸进程数
第三行:%Cpu(s) ——描述了 CPU 正在执行的进程的特性——
us:运行(未调整优先级的)用户进程的 CPU 时间
sy:运行内核进程的 CPU 时间
ni:行已调整优先级的用户进程的 CPU 时间
id:CPU 空闲时间百分比
wa:用于等待 IO 完成的 CPU 时间
hi:处理硬件中断的 CPU 时间
si:处理软件中断的 CPU 时间
st:Steal time 虚拟机被hypervisor 偷去的 CPU 时间
第四行:MiB Mem ————物理内存的使用情况————
total、free、used、buff/cache 物理内存总量、空闲内存、已使用的物理内存、用作内核缓存的内存量
第五行:MiB Swap ————虚拟内存的使用情况————
total、free、used、avail Mem 交换区总量、空闲交换区、已使用的交换区、可用于进程下一次分配的物理内存数量
  • 空白行下面(各个进程的数据,每行描述一个进程。以 CPU 的使用率排序)
说明
PID 进程 ID
USER 进程所有者的用户标识
PR 进程的优先级
NI nice 值。负值表示高优先级,正值表示低优先级
VIRT 进程使用的虚拟内存总量,单位 KB。VIRT=SWAP+RES
RES 常驻内存。进程使用的、未被换出的物理内存大小,单位 KB。RES=CODE+DATA
SHR 共享内存大小,单位 KB
S 程状态。D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程
%CPU 自从上一次更新时到现在任务所使用的 CPU 时间百分比
%MEM 进程使用的可用物理内存百分比
TIME+ 任务启动后到现在所使用的全部 CPU 时间,精确到百分之一秒
PPID 父进程 ID
UID 进程所有者的用户 ID
TTY 动进程的终端名。不是从终端启动的进程则显示为“?”
SWAP 进程使用的虚拟内存中,被换出的大小,单位 KB
TIME 进程使用的 CPU 时间总计,单位秒
COMMAND 运行进程所使用的命令。进程名称(命令名/命令行)

2)top 操作(交互)

与 less 和 vim 相似,top 也是以原始模式工作(完全接管命令行和屏幕)。

常用按键(交互) 说明
1(数字1) 显示 CPU 详细信息,每核显示一行
d seconds、s seconds 修改刷新频率,单位(秒)
f 进入另一个界面,编辑视图中的显示字段,还可以改变字段的位置
h、? 显示帮助信息
k pid 终止一个进程( pid 为进程 ID)
n number 指定进程列表显示行数:number,默认为满屏行数
u username 输入用户标识,显示用户标识相关的任务
q、^C 退出 top 程序
l(小写L) 隐藏/显示 第 1 行负载信息
t 隐藏/切换 第 2~3 行 CPU 信息
m 隐藏/切换 第 4~5 行内存信息
H 显示线程(把第二行的 Tasks 信息切换为 Threads 信息)
M 根据使用物理内存百分比(%MEM)进行排序
P(大写P) 根据 CPU 占用时间(%CPU)进行排序
T 根据时间/累计时间(TIME+)进行排序
R 切换排序顺序:正常排序/反向排序;
b 打开/关闭 加亮效果
x 打开/关闭 排序列的加亮效果(打开后可以通过Shift+>Shift+<按键来改变排序列)
y 打开/关闭 运行态进程的加亮效果
z 改变颜色

2、显示进程树:pstree

1)概念

pstree 是一个对于理解进程之间的关系非常有用的工具。

前面我们已经了解,每个进程(除了第一个)都有一个父进程。因此,可以这样假设,即系统中的所有进程都被安排到一个大型的树型结构中,而初始进程位于树的根部。我们称这样的数据结构为进程树(process tree),并使用它们来表示父进程及其子孙之间的连接。

pstree 程序可以用来显示系统进程树的任何部分。例如,从初始化进程开始显示整个进程树,或者基于特定的 PID 或者用户标识显示一个子树。(display a tree of processes)

2)最简单的例子

默认情况下,pstree 从初始化进程开始,绘制整个进程树。

[nosee@instance-4 ~]$ pstree
systemd─┬─2*[agetty]
        ├─apache2───6*[apache2]
        ├─chronyd───chronyd
        ├─cron
        ├─dbus-daemon
        ├─dhclient
        ├─google_guest_ag───11*[{google_guest_ag}]
        ├─google_osconfig───9*[{google_osconfig}]
        ├─haveged
        ├─mysqld───37*[{mysqld}]
        ├─php-fpm7.3───2*[php-fpm7.3]
        ├─rsyslogd───3*[{rsyslogd}]
        ├─sshd───sshd───sshd───bash───pstree
        ├─systemd───(sd-pam)
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-udevd
        └─unattended-upgr

注意上面例子的n*表示法,意味着n个完全相同的子树(子进程)。

注:在 Debian 系统中,初始化进程名称为 systemd,而不是 init。

3)语法选项

语法:pstree [options] [pid|user]

  • -a,显示每个进程的整个命令行,而不只是程序的名称。
  • -c,不压缩相同的子树(子进程)。
  • -n,按 PID 进行排序(默认情况,在进程树中同一层是根据进程名称排序)。
  • -p,显示每个进程的 PID。
  • -u,当子进程以不同于父进程的用户标识运行时,显示出子进程的用户标识
  • -A,使用纯 ASCII 字符绘制树的分支。
  • -G,使用特殊行绘制字符(默认)。

例:指定用户标识

[nosee@instance-4 ~]$ pstree -pa nosee
sshd,768   
  └─bash,769
      ├─pstree,987 -pa nosee
      └─vim,911 letter.txt

systemd,754 --user
  └─(sd-pam),755

例:指定 PID

[nosee@instance-4 ~]$ pstree -pa 769
bash,769
  ├─pstree,946 -pa 769
  └─vim,911 letter.txt

3、向进程发送信号:kill

kill 程序有两种应用:终于程序,以及向程序发送信号。(send a signal to a process)

1)终止程序(kill 与 kill -9)

语法:kill [-9] [pid...|%jobid...]

例:默认不带选项

[07:01 @nosee ~/Desktop]$ jobs -l
[1]+  5273 Stopped                 vi test1
[07:02 @nosee ~/Desktop]$ kill 5273
[07:02 @nosee ~/Desktop]$ jobs -l
[1]+  5273 Stopped                 vi test1
[07:02 @nosee ~/Desktop]$ fg %1
vi test1
Vim: Caught deadly signal TERM
                              Vim: Finished.
Terminated
[07:03 @nosee ~/Desktop]$ jobs -l
[07:03 @nosee ~/Desktop]$ 

从上面可以发现,当使用命令kill 5273去杀死一个进程时,这个进程并没有立即消失。当我想把该作业移至前台才发现它已经被杀死了。(后面解释原因)

例:使用选项-9

[nosee@instance-4 ~]$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
nosee     1237  1236  0 21:28 pts/0    00:00:00 -bash
nosee     2542  1237  0 22:28 pts/0    00:00:00 less letter.txt
nosee     2630  1237  0 22:31 pts/0    00:00:00 ps -f
[nosee@instance-4 ~]$ kill -9 2542
[nosee@instance-4 ~]$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
nosee     1237  1236  0 21:28 pts/0    00:00:00 -bash
nosee     2631  1237  0 22:32 pts/0    00:00:00 ps -f

当使用 kill 程序来终止进程时,我们也称为杀死了该进程。

例:杀死一个作业

[nosee@instance-4 ~]$ jobs -l
[1]+  2685 Stopped                 vim letter.txt
[nosee@instance-4 ~]$ kill -9 %1

[1]+  Stopped                 vim letter.txt
[nosee@instance-4 ~]$ 
[1]+  Killed                  vim letter.txt

使用-9选项会发送一个“强制杀死”的信号,一般这是杀死一个进程最后的选择,因为它杀死进程的过程非常快。使用kill -9杀死进程时,并不允许进程释放正在使用的资源。例如,进程可能没有关闭文件(可能导致数据丢失)、没有释放内存等。还可能导致产生不受约束的子进程(这种进程不能正确死亡)。

尽管内核通常会清理这种混乱,但是在使用kill -9最好先尝试其它的方法。

2)kill 与 kill -9 的区别

kill 命令默认的信号就是15,也就是kill -15,被称为优雅的退出。
当使用kill -15时,系统会发送一个SIGTERM的信号给对应的程序。当程序接收到该信号后,具体要如何处理是自己可以决定的。

这时候,应用程序可以选择:
-- 立即停止程序
-- 释放响应资源后停止程序
-- 忽略该信号,继续执行程序

因为kill -15信号只是通知对应的进程要进行"安全、干净的退出",程序接到信号之后,退出前一般会进行一些"准备工作",如资源释放、临时文件清理等等,如果准备工作做完了,再进行程序的终止。但是,如果在"准备工作"进行过程中,遇到阻塞或者其他问题导致无法成功,那么应用程序可以选择忽略该终止信号。

这也就是为什么我们有的时候使用 kill 命令是没办法"杀死"应用的原因,因为默认的 kill 信号是SIGTERM(15),而SIGTERM(15)的信号是可以被阻塞和忽略的。

kill -15相比,kill -9就相对强硬一点,系统会发出SIGKILL信号,他要求接收到该信号的程序应该立即结束运行,不能被阻塞或者忽略,可以称为必杀命令。

所以,相比于kill -15命令,kill -9在执行时,应用程序是没有时间进行"准备工作"的,所以这通常会带来一些副作用,数据丢失或者终端无法恢复到正常状态等。

3)向程序发送的信号

kill 程序的目的就是支持一种不同类型的 IPC(进程间通信),具体而言,就是发送一个非常简单的消息,这种消息称为信号。信号只不过是发送给进程的一个数字,从而让进程知道发生了哪些类型的事。进程会识别信号,从而决定执行什么动作,当进程这样做时我们称进程捕获了信号。

常用的信号:

信号信号 信号名称 缩写 描述
1 SIGHUP HUP 中止:注销或者终端失去连接时发送给进程
2 SIGINT INT 中断:当按下^C键时发送
9 SIGKILL KILL 杀死:立即终止
15 SIGTERM TERM 终止:请求终止
18 SIGCONT CONT 继续:恢复挂起的进程,由fgbg发送
19 SIGSTOP STOP 停止(挂起):当按下^Z键时发送

信号用于简单的,但是重要的进程间控制形式。当使用kill向进程发送信号时,可以使用信号的编号、名称或者缩写指定。如果使用名称或缩写,则一定要使用大写字母。

例:使用kill -l命令查看所有信号

[07:43 @nosee ~/Desktop]$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    

注:出于安全考虑,常规的用户标识只能向自己的进程发送信号。但是,超级用户允许向系统上的任何进程发送信号。

八、参考

  • 《Unix & Linux 大学教程》第二十六章 (美)Harley Hahn 著 张杰良 译
  • killall 、kill 、pkill 命令区别

你可能感兴趣的:(Linux深入探索15-进程和作业控制)