史上最全 Linux ps 命令详解

11,935

原文链接:blog.csdn.net/juS3Ve/arti…

一、程序员的疑惑

大概在十多年前,我当时还是一个产品经理。由于一些工作的原因,需要向运维工程师学习一些 linux 常用命令。当使用 linux ps 这个十分常用的命令时,遇到了一个小小的疑惑。有些工程师推荐使用 ps aux 的命令组合,有些工程师推荐使用 ps -aux 的命令组合,从输出结果上来看似乎也没有什么不同。考虑到如常用的 ls -l 命令在内,很多 linux 命令选项前都要加上一个短横线,这么来看似乎 ps -axu 是正确的。但是一些早期的 linux 版本,在执行 ps -axu 时又会报出如下错误 Warning: bad syntax,而 ps aux 却没有这样的报错信息,这么看来似乎 ps aux 又是正确的。查阅市面上的一些 linux 书籍,在介绍 linux ps 命令示例时,有些说用 ps aux,而有些又说用 ps -axu。实在是让我这个初学者摸不着头脑。

再后来,随着我加入运维团队对 linux ps 命令逐步深入使用,学会了查看 man page 和 help,发现其中有 ps 命令的 exmaple,果然是 ps aux 的用法是正确的。不过随着对 linux ps 命令使用的逐步深入,对 ps 命令的其他疑问越来越多。比如,我们天天在使用 ps 命令时输出的 %CPU 列到底是什么含义?为什么和 top 显示的 %CPU 的值有时候差异非常大?再比如,当我使用 ps -el 命令时,为什么我的进程名是 systemd-journald,而 ps 命令只显示 systemd-journal,弄丢了最后 1 个字母 d。

带着这么多疑惑,3 年多前我有机会简单的学习了一下 linux ps 源代码,逐步解开了上面这些疑惑。后来发现公司的很多同事也对 ps 命令有各种问题和疑问,于是想把之前对 ps 命令的学习经验总结成文档,供大家参考。希望能给大家带来帮助。

二、Linux ps 命令选项解析

Linux ps 命令功能很强大,了解 ps 命令首先需要从 ps 命令的选项格式入手。像其他很多 linux shell 命令一样,ps 命令的选项也有长格式和短格式的区别。短选项中也可以带中横线、也可以不带中横线。

根据选项长短和是否有横线的情况,ps 命令的选项可以分为以下 3 类:

  • BSD 风格语法,必须不能以中横线开头;

  • SYSV 风格语法,必须仅一个中横线开头;

  • GNU 风格语法,必须以两个中横线开头;

不过 linux ps 命令的长选项并不多,而且几乎每个长选项都有一个功能完全相同的短选项对应。在 centos7 环境运行如下命令可以见。

在本文中我们将主要介绍 BSD 和 SYSV 两种风格的 ps 命令选项。如果大家有对 GNU 风格的长选项使用的需求,那么可以参考对应的短选项语法即可。需要注意的是 GNU 风格选项都是带参数值的,例如 --sid 1。

各风格的 ps 命令选项可以混合使用,比如:

Linux ps 命令解析 SYSV 和 BSD 风格选项时,会分别将每组字符串都解析成单独的字母。以下三个实例,拆分前后的命令都是等价的。

从示例中可以看出,当 SYSV 风格语法一个中横线之后有多个字母选项时,拆分后需要给每一个字母前都加上一个中横线。也就是说 - elL 转换为 - e -l -L,而不是转换为 - e l L。

从上面例子中也可以看出,ps 命令选项除了有是否加中横线的区别,字母大小写也表现为不同的选项含义。英文字母一共 26 个,SYSV 风格选项 - A 到 - Z 和 - a 到 - z 共 52 个,BSD 风格选项 A 到 Z 和 a 到 z 共 52 个。于是 ps 命令就有一共 104 个命令选项可能性。

不同版本的 ps 命令选项的使用可能略有出入,本文主要使用主流的 centos7 上的 procps-ng version 3.3.10 版本来说明。在这 104 个命令选项中,未启用的或曾经使用过现在废弃的命令选项有如下 40 个,分别是 A、B、C、D、E、F、G、I、J、K、P、Q、R、W、Y、b、d、i、y、z、-B、-D、-E、-I、-J、-K、-Q、-R、-S、-W、-X、-Y、-b、-h、-i、-k、-r、-v、-x 和 - z。

既然是未启用或已废弃,那么运行带这个选项的 ps 命令应该会报错。试运行下大部分确实如此,不过也有几个例外,比如 ps -x 命令就不报错,可以正常输出。这样的例外选项一共有如下 7 个 - S、-X、-h、-k、-r、-v 和 - x,本文第九节会给予说明。

这 104 个命令选项中的其余 64 个选项就构成了 linux ps 命令的庞大命令选项体系,接下的内容就对他们分别给予介绍。

要查看我们当前 ps 命令的版本,就用到 V、-V 这 2 个选项。

为了本文中意思表达更加准确,这里借用数据库中的几个概念。ps 命令输出结果,约定为结果集。结果集中的每一行,我们约定为记录 (record)。结果集或记录中的每一列,我们约定为字段 (field)。

三、Linux ps 命令的记录类选项

差不多每一个工程师使用 ps 命令时应该都有这样的疑问, 使用 ps aux 时输出结果中记录行数要远大于只使用 ps 命令时(如下所示)。这其实会让很多工程师在使用 ps 命令查找需要的进程时心里很忐忑,会不会由于命令的选项使用不当导致 ps 没有列出所需要的进程信息。正是这个原因,我们首先需要搞懂 ps 命令影响记录行数的那些选项。

3.1、all_processes 选项

Linux ps 命令的记录类选项大概有 20 几个之多。有些可以列出所有的进程信息,有些按某种规则筛选显示部分进程信息。如今操作系统中 awk、sed 和 grep 这些 shell 文本处理命令的功能都十分强大,我们重点还是掌握 ps 命令中那些显示所有进程信息记录的选项,其他 ps 命令过滤选项都可以通过 shell 文本处理命令(awk、sed 和 grep)间接实现。

Linux ps 命令显示所有进程信息的选项只有 2 个,即 SYSV 风格的 - e 和 - A。相比之下,-e 更容易记忆和书写,请大家牢记这个 - e 选项。

大家知道,ps 命令的所有信息都是 linux kernel 生成,并通过 / proc / 目录输出给用户空间的。在 / proc / 目录下,每一个以数字开头的目录,就对应一个进程信息。既然如此,通过如下命令便可一目了然。

参数 - e 和 - A 显示的进程记录数确实和 proc 目录下的所有进程目录数一致。

3.2、simple_select 选项

Linux ps 命令的 simple_select 选项一共 5 个,具体包括 - a、-d、a、g 和 x。他们包括 2 个 SYSV 风格和 3 个 BSD 风格选项。2 个 SYSV 风格和 3 个 BSD 风格的选项不能同时使用,否则会报错。2 个 SYSV 风格或 3 个 BSD 风格内部可以组合使用,具体的组合可能性有 - ad、ga、ax、gx、agx。这里值得注意的是这种字母组合选项绝对不是单字母选项筛选规则的简单组合,ps 命令给这几种组合赋予了新的筛选规则。

Linux 环境下的 ps 命令,会对 BSD 风格 simple_select 选项部分做 2 个特殊处理:

  • 在原来的 BSD 风格 simple_select 情况下,再额外增加一个 g 选项;

  • 如果已经有 a、g 和 x 三个选项都出现了,那么就直接替换为 - e 选项;

按照这 2 个特殊处理规则,ps aux 选项组合等价于 ps auxg,等价于 ps agx u,等价于 ps -e u。

总结下来,ps 命令 simple_select 选项只有 6 种组合情况 - a、-d、-ad、g、ga、gx。每一种选项都赋予一个位图值。ps 命令通过位图计算来实现它的筛选规则。比如 g 选项的位图值 select_bits 是 0x0a0a,下面以 g 选项为例说明。

关于 8、4、2、1 的含义(关于 tty、session、tgid 和 euid 字段含义参考第七节):

  • “8” tty 值等于当前进程 tty 值的进程;

  • “4” tty 值为空的进程;

  • “2” session 值等于当前进程 tgid(pid) 值的进程;

  • “1” euid 值等于当前进程 euid 值的进程;

很明显 “4” 这位都缺失,1 这位都存在,那么 g 选项的含义就是:显示所有 tty 值存在的且 euid 值等于当前进程 euid 值的进程。对此持怀疑态度的同学可以通过如下 2 个命令进行验证。

同样的分析方法,其他几个选项和选项组合的含义:

  • 选项 - a 含义:显示所有 tty 值存在的且 session 值不等于当前进程 tgid(pid) 值的进程;

  • 选项 - d 含义:显示所有 session 值不等于当前进程 tgid(pid) 值的进程;

  • 选项组合 - ad 含义:显示所有 tty 值存在的或 session 值不等于当前进程 tgid(pid) 值的进程。换句话说,被过滤掉的是所有 tty 值为空的且 session 值等于当前进程 tgid(pid) 值的进程;

  • 选项组合 ga(或选项 a)含义:显示所有 tty 值存在的进程;

  • 选项组合 gx(或选项 x)含义:显示所有 euid 值等于当前进程 euid 值的进程;

3.3、selection_list 选项

这类选项比较容易理解,都是根据进程的某个属性值对进程进行筛选。他们大多需要一个选项的参数,而且也都有功能完全一样的 GNU 风格的长选项对应。此类选项一共 13 个,主要分为如下几组:

  • 进程 ID 选项,查询 PID 值为一个或几个 PID 值范围的进程信息。

  • 进程会话 (session)ID 选项,有关 SessionID 可以参见 8.1 小节。

  • 用户 ID 选项,参见 8.7 小节。

  • 用户组 ID 选项,参见 8.7 小节。

  • 进程名称选项,显示符合当前进程名称参数的进程。这里需要注意,当进程名参数值字符串长度大于 15 时,只是用其前 15 位作为匹配条件,参见 8.2 小节

  • 进程终端 (tty) 选项

这些选项不但可以单独使用,还可以组合使用(如下所示)。需要注意的是这些选项之间的组合是逻辑或的关系,即或者符合 - u 选项条件或者符合 - p 选项条件。

3.4、特殊选择选项

  • 当不希望结果中出现标题页头这一行信息时,h 选项可以隐藏 ps 输出结果中的标题栏。

  • 如果我们只希望列出运行中的 R 状态和 D 状态的进程,r 选项选中时将只显示其他筛选条件过滤后的结果集中的 R 和 D 状态进程。帮助手册上写只筛选 R 状态是不正确的,这里也包括 D 状态进程的筛选。

  • 有时候如果我们需要显示的进程记录不好筛选,但是他的补集却很容易筛选。那我们可以使用 - N 选项。选中此选项时,将以系统中所有进程(ps -e 结果)为全集对前述条件筛选后的结果取补集,即只显示原来不显示的记录。下面的例子中,去掉标题栏一行信息后,472 等于 456 加 16,说明全集等于本集加补集(如下示例所示)。

3.5、记录类选项的作用顺序

本节前几部分全面的介绍了记录筛选类选项,本小节将对这些选项的综合作用顺序进行一个系统介绍。

记录类选项是对 / proc / 目录下的进程信息逐条筛选过滤的:

  • 首先判断是否有 all_processes 选项。如果有 all_processes 选项,则本条记录被选择。如果没有 all_processes 选项,则继续下一规则。

  • 其次判断是否有 simple_select 选项。如果有 simple_select 选项,则使用 simple_select 选项规则判断本条记录是否被选中。如果没有被 simple_select 选项选择,则继续下一条规则。

  • 然后判断是否有 selection_list 选项。如果有 selection_list 选项,则使用 selection_list 选项判断本条记录是否被选中。如果没有 selection_list 选项,则继续下一条规则。

  • 然后判断选项中是否有 BSD 风格的选项。如果有 BSD 风格选项,则使用 simple_select 类的选项 g 规则判断本条记录是否被选中。如果没有 BSD 风格选项,则进入下一条规则。

  • 此时不论 ps 命令有 SYSV 风格选项还是仅无选项 ps 命令,都会使用一个新的 simple_select 选项位图进行筛选过滤,位图值为 0xaa00。如果还没有被选中,则彻底失去被选中的机会。位图值 0xaa00 的意义是:所有 tty 值等于当前进程 tty 的且 euid 值等于当前进程 euid 值的进程。

  • 接下来再看是否有 r 选项。如果有 r 选项则以上 5 个环节被选中的记录中,只有 R 和 D 状态的记录才能被继续选中。如果没有 r 选项,则以上 5 个环节中被选中的记录,都继续被选中。

  • 最后看是否有 - N 选项,如果有 - N 选项则以上第 6 个环节选中的将不被选中,第 6 个环节未选中的将被选中。如果没有 - N 选项,则以上第 6 个环节中被选中的记录,还继续被选中。

  • 选项 h 单独生效,如果有 h 选项则取消结果集的标题栏,如果没有 h 选项则标题栏保持输出。

四、Linux ps 命令输出结果排序选项

上面一章介绍了记录类选项的使用,了解了如何筛选符合我们要求的记录集。如果我们需要对输出结果进行排序,那么 ps 命令也给我们提供了 3 个选项,分别是 k、f 和 - H。

4.1、字段排序选项

选项 k 可以让我们以某个字段为条件对输出结果进行排序,并且还可以使用 +- 符号设置升序排序还是降序排序。

选项 k 还可以使用多个字段同时对结果集排序,从输出结果可以看到,先按 ppid 进行升序排序,ppid 值相同时,再按 rss 值进行升序排序。

4.2、树形排序选项

每一个进程都有一个父进程,所有用户空间的进程的最终父进程都是 1 号进程,所有内核空间的线程的最终父线程都是 2 号线程。这样所有进程按照父子进程的关系就可以构成 2 个树形结构。选项 f 和 - H 就是实现这个树形排序功能的 2 个选项。

从上面的结果中不难看出,选项 f 是使用 ACSII 码对父子进程进行关联,选项 - H 是使用 tab 空格对父子进程进行关联。

五、Linux ps 命令线程展开选项

前面章节主要是说明了如何筛选和显示进程的信息。同一个进程有时候还会起多个线程,同样内核也在 / proc / 目录下显示了进程的线程信息,如下所示。

Linux ps 同样提供了一组选项可以将每个进程的线程信息详细展现,这组选项包括 H、-L、-T、M、m 和 - m。在讲解这些选项之前,我们先看一个小测试。

同样为了统计的准确,用 h 选项去掉标题栏信息。其中最后一个 486 的值是 ps -e h 的记录数,说明当前系统有 486 个进程。非常巧的是 486 恰巧等于 1217 减去 731 的值。从这里我们可以了解到 H、-L 和 - T 这 3 个选项记录数都是 731,M、m 和 - m 三个选项记录数都是 1217。找一个起了多线程的进程查看下具体输出内容。

选项 - L 的输出可以看到一共 4 行输出结果,第一行 PID 等于 LWP(线程 ID)的值,说明是线程组的主线程(即进程)。其余三个线程 ID 各不相同,但 PID 值都和主线程的 PID 值一样,说明是同一线程组的普通线程。

第二组三个选项单纯的显示不便识别,我们这里先引入一个后面讲解的 O 选项,额外增加一个输出值 LWP。

可以看到一共 5 行输出结果,对照上面的输出我们可以判断出,第二组选项除了把线程组中的 4 个线程分别显示之外,又额外增加了一行内容专门用于显示这个线程组(即进程)的信息。我们再回头看前面的 1271 减去 731 等于 486 应该就很容易明白了。

在 H、-L 和 - T 之间,以及 M、m 和 - m 之间,输出信息也略有不同,不过这些都是数据项和格式的不同,后面有专门章节介绍。

六、Linux ps 命令的字段选项

6.1、字段组合类通用选项

很多人在使用 ps 命令时都会注意到,在我们输入不同的命令组合时,ps 命令输出结果中列的数据项并不统一。比如下面 2 个命令。

Linux ps 命令的 aux 选项组合输出 PID、%CPU、%MEM、RSS、TIME 等数据项,ps 命令的 - el 选项组合输出 PID、PPID、WCHAN、TIME、CMD 等数据项。首先一个问题就是,ps 命令一共有多少数据项可以输出。这个问题很好回答,通过 L 选项很容易获取,一共有 168 个数据输出项。

其次的一个问题就是,是什么决定了 ps aux 命令输出结果中恰恰包含 USER、PID、%CPU、%MEM、VSZ、RSS、TTY、STAT、START、TIME 和 COMMAND 这 11 个数据项呢。原因是 ps 命令中有一些选项用来对数据字段进行固定组合的作用。其中 aux 中的 u 选项就固定包含了以上 11 个数据输出项,并且他们的显示顺序也已经固化在代码中。

Linux ps 命令这种字段组合类选项一共 15 个。其中 6 个选项用途比较广泛,其余 9 个选项都主要适合在查询某一种问题时使用。

本小节先介绍 6 个通用选项:

  • 面向用户角度来显示进程状况,其中的 %CPU、%MEM、VSZ 和 RSS 字段都是非常常用的信息。

  • 采用详细格式显示进程状况,此类选项所显示字段主要为一些常用字段信息。

  • 采用完整格式显示进程状况,此类选项所显示字段同样为一些常用字段信息。

6.2、字段组合类专用选项

本小节先介绍 9 个适合特殊用途的专用选项:

  • 采用作业 (job) 控制的格式显示进程状况,字段 PPID、PID、PGID、SID 和 TPGID 都是此选项的关键信息。

  • 采用旧式的 linux i386 寄存器格式显示进程状况,很明显此选项特点是 STACKP、ESP 和 EIP 这些寄存器信息。

  • Linux 或 sunos 操作系统中会额外增加 PSR 字段的显示,PSR 字段是指当前进程被调度到的 CPU 核序号。

  • 采用程序信号的格式显示进程状况,此选项特色字段是 PENDING、BLOCKED、IGNORED 和 CAUGHT 字段。很显然,当我们进行 linux 信号编程时,使用此选项非常有用。

  • 采用虚拟内存的角度显示进程状况,此选项特色字段包括 MAJFL、TRS、DRS、RSS 和 %MEM。

  • 打印操作系统强制访问控制(SELinux)的安全标签信息,此选项特点是 LABEL 字段信息。

也许有人已经观察到了,以上字段组合选项,不论哪个都会固定的有几个字段总是出现,比如 PID、TTY、TIME 和 CMD 等。下面请大家先看一下这几个例子。

从以上例子中,我们可以得出几个信息。参数为 BSD 风格时,默认都会显示 PID、TTY、STAT、TIME 和 COMMAND 这 5 个字段。参数为 SYSV 风格时,默认都会显示 PID、TTY、TIME 和 CMD 这 4 个字段。ps 命令无参数时默认为 SYSV 风格。

6.3、自定义字段选项

上一小节字段组合选项是 ps 命令为了一些常用场景固化在代码中的固定数据项组合。但是如果以上所有组合都不满足我们的要求,或者我们为了提升 ps 命令运行效率仅仅需要个别的数据项输出。那么我们可以通过 - o 或 o 选项来实现自定义数据项的输出功能。比如我们对 ps j 这个命令字段组合的输出信息不满意,我们自定义他的输出。

前文提到过,ps 命令一共可以输出 168 个字段,ps L 命令可以显示这 168 个字段的详细情况。第一列小写字母是 - o 选项的参数,可以通过逗号隔开。第二列大写字母是 ps 命令输出的结果集标题栏名称。

尽管大部分情况下 - o 参数和标题栏都仅仅是大小写的转换关系,但也有不那么完美的时候,以进程的执行命令字段为例。

这个例子至少可以说明两点,不同的说明符 (specifier,-o 选项参数) 可能输出同样的标题栏。尽管标题栏一样,但显示的内容可能是有区别的。

有些说明符还提供缩写,下表是 ps 命令有缩写的说明符和缩写的对应关系表,一共 15 个。

有了说明符的缩写之后,可以对自定义字段的输出字段之间添加自定义分隔符。区别于以往 ps 命令各输出字段都是使用空格作为分割,使用自定义分隔符之后将更方便使用 shell 数据处理命令进行解析。

前文提到所有字段组合选项都默认包含 4 个或 5 字段。如果想在自定义字段组合时也默认添加一些常用字段,而同时又省去 - o 选项参数的输入过程,那么可以使用 O 或 - O 选项。

这 2 个选项 O 或 - O,会在自定义字段之前默认增加 pid 字段,在自定义字段之后默认增加 state、tname、time 和 command 字段。

七、Linux ps 命令字段修饰选项

本节前面的选项都是决定输出结果中字段的数量和顺序,本小节将介绍几个只对输出结果中某个字段进行修饰的选项。首先来看 - w 和 w 选项。

这个实例说明,当屏幕不是很宽时,如果进程命令很长,默认情况下,会将命令超出屏幕的部分截取掉,这样势必会影响系统管理员调查问题,使用 w 或 - w 选项,就会将完整的进程命令信息显示,多出的部分换行显示。有的时候为了效果好一点,建议我们可以多使用几次 w 选项,比如 ww、www 或 wwww。

接下来我们再来看一下 c 和 e 选项。

选项 u 的 COMMAND 字段,默认会输出进程路径和执行参数信息。从上面的例子可以看出,选项 c 可以使选项 u 的 COMMAND 字段更加精简,只保留进程名称。选项 e 可以使选项 u 的 COMMAND 字段更加丰富,增加进程环境变量的相关内容。

当选项 S 被选中,ps 命令在显示如下 6 个字段信息时,会将已经死亡的子进程信息也包含计算在内,如果未选中 S 选项将不会计算这些已经死亡的子进程信息 。快速同时执行如下 2 个命令,即可看出这 6 个字段值,选中 S 值后比之前有明显增大。具体字段含义请参考 8.5 小节和 8.6 小节。

Linux ps 命令的字段中有个 wchan 字段(wchan 相关含义参考 8.8 小节)。默认情况下 ps 命令会输出 wchan 的符号信息,如果希望输出 wchan 的原始数值信息,可以使用 n 选项。请比较如下 2 个命令,添加 n 选项前后第 11 个字段的输出差别。除了 wchan 之外,选项 n 还可以将原本输出 user name 的地方转换为 user id 输出。

字段 wchan 的数值信息和符号信息的映射关系通过操作系统中一些 System.map 文件完成,如果用户需要使用自定义的 System.map 文件,可以通过选项 N 或 - n 完成,如下示例。

八、Linux ps 命令常用字段

前文提到 linux ps 一共最多可以输出 168 个字段,通过 ps L 命令可获取详情。通过字段相关选项可以获取符合用途的字段组合。为了让大家对 ps 命令的理解更加深入,本节会深入介绍一些常用的输出字段的含义。

按照这些常用字段的内在关系,我们将分为以下八个小结介绍。

8.1、进程 ID 类字段

进程 ID 类字段是 ps 命令字段中最基础的一类。为了能更加形象的说明这些 ID 的关系和含义,请大家按照如下命令顺序操作。

对以上输出结果的字段逐条说明:

  • 字段 tid 表示进程的线程 ID,可以看出每个线程的 tid 都不相同。

  • 字段 nlwp 表示当前线程组中的线程个数,以上命令都是单线程进程,因此此值均为 1。

  • 字段 pid 表示进程 ID,也可以看出每个进程的 PID 都不相同。

  • 字段 pgid 表示进程组 ID,上面的例子中除了和 setsid 结合的 vmstat 命令,其余三组通过 shell 管道连接起来的命令的 pgid 都相同。比如 tail、awk 和 nl 命令的 pgid 都为 1384,且 pgid 值为组内第一个命令 tail 的 pid 值;iostat、sed 和 fold 命令的 pgid 都为 1388,且 pgid 值为组内第一个命令 iostat 的 pid 值。

  • 字段 sid 表示会话 ID。上面的例子中最后一行是第三个登录终端的 shell。第一个登录终端上的所有进程 sid 都相同,且为登录 shell 的 pid 值 1351;除了和 setsid 结合的 vmstat 命令,第二个终端上的所有进程 sid 都相同,且为登录 shell 的 pid 值 1394。

  • 字段 tpgid 表示进程连接到的 tty(终端) 所在的前台进程组的 ID。除了 vmstat 进程之外,第二个终端上的所有进程 tpgid 也都等于登录 shell 的 pid 值 1394。但是第一个终端上的所有进程 tpgid 却都等于第一个终端上又启动的那个 shell 的进程 id 值 1370。充分说明了 tpgid 值是链接着终端的前台进程组 ID 值。

  • 字段 ppid 表示父进程 ID。

  • 最后我们再来解释和 vmstat 结合的 setsid,setsid 就是使和它结合的 vmstat 脱离原来的会话,脱离之后 pgid 和 sid 都等于了 vmstat 进程的 pid,同时父进程也由 1 号进程托管。此时也没有了所依附的终端,tpgid 统一等于 - 1。Linux 上的所有守护进程的 tpgid 值都是 - 1。

进程 ID 类字段的别名情况:字段 spid 和字段 lwp 是字段 tid 的别名,字段 tgid 是字段 pid 的别名,字段 pgrp 是字段 pgid 的别名,字段 sess 和字段 session 是字段 sid 的别名。

8.2、命令名字段

命令名相关的字段一共有 3 组,如下所示。

命令名字段的别名情况:字段 comm 和字段 ucomm 是字段 ucmd 的别名,字段 args 和字段 command 是字段 cmd 的别名。

建议大家掌握 ucmd 和 cmd 这 2 个字段,cmd 为长命令名字段,ucmd 为短命令名字段,可以理解为 unadorned cmd(未加修饰的命令名)。

前文提到过如果程序名称长度超过 15 位,ps 命令的短命令名无法完整显示 16 位及以上的部分。下面看一个小例子来说明这个问题。

从上面的例子可以看出,当程序名称超过 15 位时,确实短命令名无法显示完整的程序名称,只显示了 15 位。进一步查看 / proc/8040 / 目录,可以发现如下信息。

查询内核代码,可以发现 comm 值取自内核 struct task_struct 结构体的 comm 属性字段。

这就告诉我们通过 ps 命令短命令字段无论如何都无法输入超过 15 位的程序名称,原因是内核数据结构原生就只支持 15 个字符长度的程序名称。

除此之外上面的例子还给我们另外一个启示,如果通过使用 SYSV 风格的短命令名就可以满足使用要求(如 ps -el),那就尽量不要使用 BSD 风格的长命令名(如 ps -e u,即 ps aux)。长命令名需要依赖内核中健康的文件系统,而当文件系统工作不正常时,往往短命令名却可以不受影响。所以我们在实际生产中偶尔会发现系统中有大量 ps aux 进程 D 住的情况。

8.3、进程状态字段

进程状态类字段一共有三个,分别是 s、state 和 stat,如下所示。

字段 s 和 state 互为别名,值为单字节进程状态。这里重点介绍一下 stat 选项的多字节进程状态,查看一下 ps 命令关于这个多字节进程状态的 c 语言代码。

根据以上源代码,我们来逐条解释:

  • 字符'<'表示 nice 值小于 0,nice 值最小为 - 20。进程特性 nice 值允许进程间接的影响内核调度算法。所谓 nice 就是指对其他进程的谦让程度,显然比 0 越小就越不谦让,比 0 越大就越谦让。顾名思义,越谦让的君子在 CPU 调度过程中占用 CPU 的时间会越少,反之不谦让的进程相比较可能占用更多的 CPU 时间。因此字符'<'表示此进程可能在调度过程中获得优势。

  • 字符'N'表示 nice 值大于 0,nice 值最大为 19。因此字符'N'表示此进程可能在调度过程中不能获得优势。

  • 字符'L'表示进程 vm_lock 值为真,即此进程有内存页被锁在内存中,这些内存页不能通过换页换出。

  • 字符's'表示进程的 tgid(pid) 值等于进程的 session(sid) 值,这说明当前进程是会话的 leader,参考 8.1 小节。

  • 字符'l'表示进程中的线程数量大于 1,这说明当前进程是一个多线程程序。

  • 字符'+'表示进程的 pgrp(pgid) 值等于进程的 tpgid 前台进程组 ID,这表示当前进程在前台进程组中。

8.4、时钟 (系统) 时间类字段

时钟时间(系统时间)类字段,记录了进程开始时间点和执行的时长信息,这类字段一共有 6 组。其中 4 个记录进程开始时间点,2 个记录进程执行时长信息,示例如下。

从自动化运维脚本的角度,lstart 字段的输出信息格式更加规范便于解析,etimes 字段作为一个正整数也可以直接使用。字段 start_time 是字段 stime 的别名。下面给一个使用的例子。

字段 lstart 的输出固定占用了 2 2 6 这 5 列信息,这样在其后的 etimes 字段也固定占用了第 $7 列。使用 awk 结合 date 命令很方便的就将进程开始时间转化为时间戳格式。

8.5、CPU 时间和使用率字段

CPU 时间和使用率类字段一共有 5 组,记录了进程所消耗的 CPU 时间片和 CPU 使用率信息,示例如下。

字段 bsdtime 的输出相比较 cputime 更加方便转换为正整数的秒数。字段 cp 的单位是千分比,不能超过 999。字段 c 是百分比,不能超过 99。

进程 CPU 时间类字段别名:字段 atime 和字段 time 是字段 cputime 的别名;字段 util 是字段 c 的别名;字段 %cpu 是字段 pcpu 的别名,但是 % 字符在 crontab 中使用并不友好,推荐使用 pcpu。

下面来说明一下 ps 命令的 CPU 利用率的含义,先运行一个例子。

从这个命令运行的结果可以看出 bsdtime 除以 etimes 的值转换为百分比后,基本和 pcpu 的值相等。这就足以说明 ps 命令的 CPU 利用率字段指标是指从进程开始运行以来进程所耗费的 CPU 时间片占时钟时间的百分比。有时候这个值大于 100%,那是因为进程启用了多线程,很多时候有多核在同时使用 CPU 时间片。

看过 top 命令源码可以知道,top 命令默认是取最近 3 秒钟进程所耗费的 CPU 时间片除以 3 秒钟的百分比值。我们可以设想一种场景,如果一个进程已经运行了 1 年以上,平时都很稳定。但是刚刚就在十几分钟前突然运行大量线程,占用大量 CPU 资源。结果你在你刚刚登陆系统之前 10 秒钟这些运行的线程都结束了。那么你不论是通过 top 命令的 CPU 利用率指标,还是 ps 命令的 CPU 利用率指标都无法发现刚才作怪的这个线程的迹象。

8.6、进程内存相关字段

进程内存相关字段也 ps 命令字段中非常重要的一类,主要分为如下 9 组,示例如下。

对以上输出结果的字段逐条说明:

  • 字段 vsz(virtual memory size)表示进程所申请的虚拟地址空间的内存大小,单位 KB。在 64 位系统中,每个进程都有 128Tb 大小的堆内存虚拟地址空间的内存空间大小。vsz 值并不反映进程占用的真正物理内存大小。

  • 字段 rss(resident set size)表示进程真正占用了的物理内存大小,单位 KB。

  • 字段 pmem 表示进程占用的物理内存大小 (rss) 占本机总物理内存大小百分比。

  • 字段 trs(text resident set size)表示用于可执行代码的物理内存大小,约等于进程的程序尺寸大小。

  • 字段 drs(data resident set size)表示可执行代码之外的内存大小,实际基本等于 vsz 减去 trs 的值。

  • 字段 size 表示如果进程交换到磁盘所需的交换空间大小。

  • 字段 sz 表示进程在物理页面中的核心镜像的大小。

  • 字段 minflt 表示此进程中发生的次缺页异常的数量,下面详细介绍。

  • 字段 majflt 表示此进程中发生的主缺页异常的数量。

进程内存相关字段别名:字段 m_drs 和字段 dsiz 是字段 drs 的别名,字段 vsize 是字段 vsz 的别名,字段 m_size 是字段 sz 的别名,字段 rssize、字段 rsz 和字段 sgi_rss 是字段 rss 的别名,字段 m_trs、字段 trss 和字段 tsiz 是字段 trs 的别名,字段 %mem 是字段 pmem 的别名,字段 min_flt 是字段 minflt 的别名,字段 maj_flt 和字段 pagein 是字段 majflt 的别名。

下面通过一个例子来加深对缺页异常的理解。

可以看出字段 rss 和字段 minflt 的比值趋近于 4。操作系统管理内存的基本单元是 4096 字节大小的页框,当进程访问尚未有物理内存建立页表映射关系的虚拟内存地址值时,会产生一次缺页异常。在缺页异常处理过程中会为虚拟内存页分配一个物理内存页并建立映射。所以每一次缺页异常就会分配 4096(4kb)字节的物理内存,这样 rss 和 minflt 的比值当然就是 4 了。如果分配之后又有释放,后面再次分配,会使这个比值逐步小于 4。如果这个比值过于小,那我们就有充足理由怀疑用户程序代码在内存管理上存在重大问题。

8.7、进程凭证类字段

进程凭证类字段一共有 30 多个,其中大部分可以汇总为如下表格。

其中有些凭证是另外凭证的别名,比如 uid 是 euid 的别名,svuid 是 suid 的别名,fsuid 是 fuid 的别名。因此这些凭证字段简单的可以归纳为如下 4 方面凭证:

  • 实际凭证 (real user ID):一般表示进程的创建者,属于哪个用户创建。

  • 有效凭证 (effective user ID):表示进程对于文件和资源的访问权限,具备等同于哪个用户的权限。

  • 保护凭证 (saved set-user-ID):set-user-ID 的保存 ID。

  • 文件系统凭证 (file-system user ID):已经基本废弃。

8.8、WCHAN 字段

WCHAN 类字段一共 3 个 nwchan、wchan 和 wname。WCHAN 就是 waiting channel 的意思,进程正在休眠的内核函数的函数符号名称。R 状态进程此字段值为 “-”。

字段 wchan 和 wname 都显示的是内核函数的函数符号名称信息,默认只显示 6 个字节。如果希望显示完整的函数名称,可以通过在字段名称后加冒号再加宽度数值的方式显示更丰富信息,即 wchan:25。

字段 nwchan 显示的是内核函数符号的指针地址数值信息。一个完整的 64 位的内核函数指针地址是一个 16 位的十六进制值,前 10 位固定为'ffffffff81',因此 ps 命令的 nwchan 字段只显示出了后 6 位的十六进制值。比如指针地址是 ffffffff8124bb7e,那么 nwchan 显示 24bb7e。如果后 6 位的高位有 0,则省略掉 0 的显示。

九、Linux ps 命令选项容错机制

Linux ps 命令所有的选项和大多数字段都解释过了,现在该说说文章开头那个报错的 ps -axu 了。

ps 命令会提供一种选项容错机制。当用户输入的是一个 SYSV 风格参数组合后,如果参数解析失败,ps 命令会继续尝试把同样的字母组合都转换为 BSD 风格再尝试进行一次解析。比如 ps -aux 解析失败后尝试按 ps aux 解析,ps -x 解析失败后尝试按 ps x 解析。当然了,如果再次按照 BSD 风格尝试解析仍失败,那 ps 命令会最终失败报错。

事实上,能有机会被 ps 命令容错机制纠正的错误选项只有这 7 个,-S、-X、-h、-k、-v、-r 和 - x。因为这些字母虽然没有 SYSV 风格的选项,但是却都有 BSD 风格的选项。

最后说一下,没有将 BSD 风格到 SYSV 风格的容错机制,比如 SYSV 风格有 - F 选项,而 BSD 风格没有 F 选项。运行命令 ps F 还是会报错。

十、Linux ps 命令综合解析

10.1、常用选项摘要

本文对 linux ps 命令的 104 个短选项都进行了说明,其中只有一部分选项比较常用,下面根据我的经验推荐给大家重点关注:

  • 选项 - e:显示所有进程的记录,记住这个参数就可以保证把当前系统的所有进程都输出。需要筛选进程时,可以结合 grep 等文本处理命令实现目的。

  • 选项 h:选中时可以隐藏输出结果的标题栏信息,在一些自动化脚本中使用此参数可以去除页头信息。

  • 选项 k:通过此选项可以实现对输出结果的排序。

  • 选项 - L:通过此选项可以把多线程的进程展开每个线程的细颗粒度。

  • 选项 - l:或选项 l,此选项可以列出进程的最基本信息,包括 s、pid、ppid、time 和 ucmd 等字段信息。

  • 选项 u:此选项可以列出 cpu 使用率、mem 使用率、rss 内存等字段信息。

  • 选项 - o:或选项 o,通过此选项可以自定义输出符合自己需求的字段信息。

Linux ps 命令的 168 个输出字段,我们也对大部分进行了介绍。下面根据经验推荐给大家一些常用的关注。