ps 命令详解与参数实测(Ubuntu 22.04)
在 Ubuntu 22.04(procps-ng 3.3.17)上逐参数实测 ps:默认输出、aux/-ef 全列解读、STAT 状态码逐码制造、--forest 进程树、-eLf 线程视图、-o 自定义列、--sort 排序、-p/-C/-u/-t/--ppid/-a/-x 筛选、/proc 对应与 grep 陷阱,全部命令与回显真实可复现。
ps 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 ps 的每一个常用参数——默认输出、
aux/-ef全列解读、STAT 状态码逐码制造、--forest进程树、线程视图、-o自定义列、--sort排序、-p/-C/-u/-t/--ppid/-a/-x各类筛选、/proc对应关系与 grep 自身陷阱,全部命令与回显已在 Ubuntu 22.04(procps-ng 3.3.17)实测通过。
一、环境信息
本篇所有命令均在以下环境中逐条实测,回显逐字照抄:
- 操作系统:Ubuntu 22.04.5 LTS(代号 jammy)
- 内核:6.8.0-138-generic(x86_64)
- ps 版本:procps-ng 3.3.17(Ubuntu 22.04 官方仓库自带)
- CPU / 内存:12 逻辑核 / 40 GB(
free -mtotal 40066) - 实测账号:root;演示目录:
/root/demo/ps - 本机为多账户共用环境,存在少量其他账户(tester、reniceu1)历史会话遗留的僵尸进程,正文回显如实保留,并借此演示 STAT=Z 的真实形态
版本与环境信息实测回显:
$ ps --version
ps from procps-ng 3.3.17
$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
$ uname -r; uname -m
6.8.0-138-generic
x86_64
$ free -m | head -2
total used free shared buff/cache available
Mem: 40066 9519 7922 57 22624 29891
$ nproc
12
💡 说明:ps 是 procps-ng 软件包的一员,同一包内还包含 top、free、uptime、pkill、pgrep、watch 等进程管理工具,本文的对照篇《top 命令详解与参数实测(Ubuntu 22.04)》即来自同一软件包。
二、命令概览
2.1 语法与测试数据
ps(process status)用于快照式地报告当前系统的进程状态,语法极简:
ps [options]
ps 的难点不在命令本身,而在参数风格:它同时兼容三套语法体系,同一字母加不加 - 含义可能完全不同。
💡 技术拓展:ps 的三套参数风格对比——
风格 形态 典型例子 语义要点 UNIX(POSIX) 必须带 -,可合并ps -ef、ps -eLf合并短选项,如 -ef等价-e -fBSD 不带 -ps aux、ps axjf、ps u无 -的字母同样被解析为选项GNU 长选项 双连字符 ps --sort=-%cpu、ps --forest与其他 GNU 工具习惯一致 本文以最常用的
ps aux(BSD 风格)与ps -ef(UNIX 风格)两条主线展开,排序与输出定制使用 GNU 长选项;三套风格可在同一条命令中混用(如ps axjf、ps -eo ... --sort=...),但混写-aux这类写法存在语义陷阱,见 3.15 节实测。
为了让后文的状态码、线程、进程树演示有真实对象,先准备演示数据(演示目录 /root/demo/ps,测试结束后已清理):
$ mkdir -p /root/demo/ps && cd /root/demo/ps && pwd
/root/demo/ps
$ sleep 300 & S1=$!; echo S1=$S1
S1=557
$ sleep 301 & S2=$!; echo S2=$S2
S2=558
$ nice -n 10 sleep 302 & S3=$!; echo S3=$S3
S3=559
$ nice -n -5 sleep 303 & S4=$!; echo S4=$S4
S4=560
准备一个多线程演示程序 mt.py(主线程加 3 个工作线程,用于观察 STAT 的 l 标志、NLWP 列与线程视图),内容如下:
import threading, time
def worker():
while True:
time.sleep(1)
threads = [threading.Thread(target=worker, daemon=True) for _ in range(3)]
for t in threads:
t.start()
time.sleep(300)
$ python3 /root/demo/ps/mt.py & PY=$!; echo PY=$PY
PY=561
$ ps -o pid,ppid,nlwp,stat,comm -p 561
PID PPID NLWP STAT COMMAND
561 540 4 Sl python3
再通过 script 工具制造一个带真实伪终端(pts/0)的会话,用于演示 s、+ 两个 STAT 标志与 -t 终端筛选:
$ script -qc 'sleep 300' /dev/null & PTY=$!; sleep 1; echo PTY=$PTY
PTY=563
$ ps -o pid,ppid,pgid,tpgid,sid,tty,stat,comm -p 563,565,566
PID PPID PGID TPGID SID TT STAT COMMAND
563 540 540 -1 540 ? S script
565 563 565 565 565 pts/0 Ss+ sh
566 565 565 565 565 pts/0 S+ sleep
⚠️ 注意:
nice -n -5 sleep 303试图以负 nice 值(更高优先级)启动进程,本环境即使 root 也被拒绝(nice: cannot set niceness: Permission denied,详见 3.15 节),因此 560 号进程最终以普通优先级运行,后文实测它的 STAT 是S而非S<。
2.2 --help 全量输出(逐行照抄)
ps --help 不带主题时给出用法骨架与可选主题:
$ ps --help
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
ps --help all 输出全量帮助,以下按主题分段照抄并解读。
第一段:Basic options(基础选择)——控制"选哪些进程"的粗粒度开关:
Basic options:
-A, -e all processes
-a all with tty, except session leaders
a all with tty, including other users
-d all except session leaders
-N, --deselect negate selection
r only running processes
T all processes on this terminal
x processes without controlling ttys
关键差异:-e 是"全部进程",-a 只选"有控制终端的进程(除会话组长)",x 补上"无控制终端的进程",ax 组合即成为最常用的全量视图(实测见 3.11 节)。
第二段:Selection by list(按列表筛选)——本文 3.10 节的主角:
Selection by list:
-C <command> command name
-G, --Group <GID> real group id or name
-g, --group <group> session or effective group name
-p, p, --pid <PID> process id
--ppid <PID> parent process id
-q, q, --quick-pid <PID>
process id (quick mode)
-s, --sid <session> session id
-t, t, --tty <tty> terminal
-u, U, --user <UID> effective user id or name
-U, --User <UID> real user id or name
The selection options take as their argument either:
a comma-separated list e.g. '-u root,nobody' or
a blank-separated list e.g. '-p 123 4567'
要点:所有筛选参数都接受逗号分隔或空格分隔的列表,如 -p 1,2,3、-u root,nobody。
第三段:Output formats(输出格式)——控制"显示成什么样":
Output formats:
-F extra full
-f full-format, including command lines
f, --forest ascii art process tree
-H show process hierarchy
-j jobs format
j BSD job control format
-l long format
l BSD long format
-M, Z add security data (for SELinux)
-O <format> preloaded with default columns
O <format> as -O, with BSD personality
-o, o, --format <format>
user-defined format
s signal format
u user-oriented format
v virtual memory format
X register format
-y do not show flags, show rss vs. addr (used with -l)
--context display security context (for SELinux)
--headers repeat header lines, one per page
--no-headers do not print header at all
--cols, --columns, --width <num>
set screen width
--rows, --lines <num>
set screen height
关键差异:-f(全格式,含命令行)与 u(面向用户,即 aux 的格式)是最常用的两种版式;-o/--format 支持任意列组合;--forest 用 ASCII 连线画进程树。
第四段:Show threads(线程显示):
Show threads:
H as if they were processes
-L possibly with LWP and NLWP columns
-m, m after processes
-T possibly with SPID column
要点:-L 在输出中增加 LWP(线程 ID)与 NLWP(线程数)列,是排查线程问题的主力(实测见 3.7 节)。
第五段:Miscellaneous options(杂项):
Miscellaneous options:
-c show scheduling class with -l option
c show true command name
e show the environment after command
k, --sort specify sort order as: [+|-]key[,[+|-]key[,...]]
L show format specifiers
n display numeric uid and wchan
S, --cumulative include some dead child process data
-y do not show flags, show rss (only with -l)
-V, V, --version display version information and exit
-w, w unlimited output width
--help <simple|list|output|threads|misc|all>
display help and exit
For more details see ps(1).
要点:--sort 的语法是 [+|-]key[,...](- 前缀表示降序),L 可以单独列出全部输出说明符,-V 显示版本。
2.3 版本信息
$ ps --version
ps from procps-ng 3.3.17
Ubuntu 22.04 官方源的 procps-ng 版本为 3.3.17,本文全部行为均以此版本为准;不同发行版携带的 procps-ng 版本对个别警告信息与默认列宽可能略有差异。
三、参数逐项实测
3.1 无参数默认输出
不带任何参数运行 ps,它按"与执行者相同的用户 + 相同的控制终端"选择进程。本例在无控制终端的会话中执行,因此列出的是 TTY 为 ? 的本用户进程:
$ ps
PID TTY TIME CMD
1 ? 00:00:00 sleep
155 ? 00:00:00 sh <defunct>
156 ? 00:00:00 sleep <defunct>
212 ? 00:00:00 sh <defunct>
213 ? 00:00:00 sleep <defunct>
359 ? 00:00:00 sh <defunct>
360 ? 00:00:00 sleep <defunct>
535 ? 00:00:00 bash <defunct>
540 ? 00:00:00 bash
553 ? 00:00:00 ps
- 默认输出只有 4 列:PID、TTY、TIME(累计 CPU 时间)、CMD;
- TTY 显示
?表示该进程没有控制终端——守护进程与后台任务的典型形态; <defunct>即僵尸进程标记(STAT=Z),本机遗留了若干由 1 号进程托管却从未收尸的僵尸,STAT 一节将专门解读;- 最后一行
ps是 ps 进程自身——它扫描/proc时自己也在运行,这个小细节在 3.3 节还会用来演示 R 状态。
💡 说明:在交互式终端(如 SSH 登录后直接敲
ps)中,默认输出通常只有当前 shell 与刚启动的命令两三行;想看全系统进程需要ps aux或ps -ef。
3.2 ps aux 全列解读
ps aux 是运维中使用频率最高的形式:a 选出所有带终端的进程(含其他用户的),x 补上无终端进程,u 使用面向用户的列格式。先看输出前段(本机 PID 序列前段恰为一排历史遗留僵尸):
$ ps aux | head -12
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2792 1464 ? Ss 10:13 0:00 sleep infinity
tester 136 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 139 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 155 0.0 0.0 0 0 ? Zs 10:15 0:00 [sh] <defunct>
root 156 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 212 0.0 0.0 0 0 ? Zs 10:15 0:00 [sh] <defunct>
root 213 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 327 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
tester 330 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
root 359 0.0 0.0 0 0 ? Zs 10:17 0:00 [sh] <defunct>
root 360 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
再看输出后段(即 2.1 节准备的演示进程群,是本节的解读主角):
$ ps aux | tail -14
root 360 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
reniceu1 507 0.0 0.0 0 0 ? ZN 10:20 0:00 [sleep] <defunct>
root 535 0.0 0.0 0 0 ? Z 10:20 0:00 [bash] <defunct>
root 540 0.2 0.0 4880 3880 ? Ss 10:21 0:00 bash /root/ps-test.sh
root 557 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 300
root 558 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 301
root 559 0.0 0.0 2792 1652 ? SN 10:21 0:00 sleep 302
root 560 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 303
root 561 0.0 0.0 238924 8884 ? Sl 10:21 0:00 python3 /root/demo/ps/mt.py
root 563 0.0 0.0 2808 1912 ? S 10:21 0:00 script -qc sleep 300 /dev/null
root 565 0.0 0.0 2892 1712 pts/0 Ss+ 10:21 0:00 sh -c sleep 300
root 566 0.0 0.0 2792 1688 pts/0 S+ 10:21 0:00 sleep 300
root 604 0.0 0.0 7072 3036 ? R 10:21 0:00 ps aux
root 605 0.0 0.0 2824 1576 ? S 10:21 0:00 tail -14
ps aux 共 11 列,逐列含义与上表实测值的对应关系:
| 列 | 含义 | 实测对照 |
|---|---|---|
| USER | 进程属主(生效用户) | root、tester、reniceu1 |
| PID | 进程 ID | 1(1 号进程)、557 等 |
| %CPU | CPU 占用率,单核 100% 为满 | 540 号 bash 0.2、其余多为主 0.0 |
| %MEM | 物理内存占用率 | python3 0.0(RSS 约 8.9 MB / 40 GB) |
| VSZ | 虚拟内存大小(KB) | python3 高达 238924 KB,sleep 仅 2792 KB |
| RSS | 常驻物理内存(KB) | python3 8884 KB,僵尸进程为 0 |
| TTY | 控制终端 | ?=无终端;pts/0=伪终端 |
| STAT | 进程状态码 | Ss、S、SN、Sl、S+、Z、R 等(见 3.3) |
| START | 启动时刻 | 当天显示 10:21,跨年进程显示日期 |
| TIME | 累计 CPU 时间 | sleep 类进程为 0:00 |
| COMMAND | 命令名或命令行 | sleep 300、bash /root/ps-test.sh |
两个值得注意的实测细节:
- 僵尸进程的 VSZ/RSS 全为 0——进程实体已消亡,只剩内核中的一条退出信息;
ps aux输出里604 ps aux一行是 ps 自身(STAT=R),605 tail -14是管道下游进程,说明 ps 的快照覆盖了执行瞬间的整条管线。
💡 技术拓展:内存三口径——VSZ、RSS、SHR 的区别——
口径 含义 特点 VSZ 虚拟地址空间总大小 含已映射未触碰的页、mmap 预留、动态库映射,通常远大于实际占用 RSS 常驻物理内存 真正占着物理内存的页,内存告警看它 SHR 其中与其他进程共享的部分 动态库、共享内存段,多进程重复计算已剔除 实测中 python3 的 VSZ≈233 MB 而 RSS≈8.7 MB,正是"虚拟地址空间大、实际驻留小"的典型形态;判断真实内存压力看 RSS 与 %MEM。
3.3 STAT 状态码:码表与逐码实测
STAT 列由"主状态码 + 修饰符"组成。依据 procps-ng 3.3.17 的 ps(1) 手册,主状态码如下:
| 主码 | 名称 | 含义 |
|---|---|---|
| R | Running | 正在运行或在运行队列中等待 CPU |
| S | Sleeping | 可中断睡眠,等待事件(IO、信号、定时器) |
| D | Uninterruptible sleep | 不可中断睡眠,通常是磁盘 IO,信号无法唤醒 |
| Z | Zombie | 僵尸:进程已终止,父进程尚未读取其退出状态 |
| T | Stopped | 被信号(SIGSTOP/SIGTSTP)暂停,或正被调试器跟踪 |
| I | Idle | 空闲的内核线程 |
常用修饰符如下(均可组合出现):
| 修饰符 | 含义 |
|---|---|
| s | 会话组长(session leader) |
| l | 多线程(使用 CLONE_THREAD,线程数 >1) |
| + | 位于前台进程组 |
| < | 高优先级(设置了负 nice 值,非实时) |
| N | 低优先级(设置了正 nice 值) |
| L | 有锁定在内存中的页(用于实时与 IO 密集场景) |
下面逐码实测。S(可中断睡眠)与 N(低优先级)——4 个演示 sleep 与多线程 python3 的状态(560 号因负 nice 被拒而保持 NI=0,故为 S 而非 S<):
$ ps -o pid,ppid,ni,stat,comm -p 557,558,559,560
PID PPID NI STAT COMMAND
557 540 0 S sleep
558 540 0 S sleep
559 540 10 SN sleep
560 540 0 S sleep
$ ps -o pid,ppid,ni,nlwp,stat,comm -p 561
PID PPID NI NLWP STAT COMMAND
561 540 0 4 Sl python3
R(运行态)——利用"ps 扫描 /proc 时自身正在运行"的特点,用 -C ps 按命令名把 ps 自己筛出来,STAT 稳定为 R:
$ ps -o pid,stat,comm -C ps
PID STAT COMMAND
578 R ps
T(暂停态)——对演示进程发 SIGSTOP 使其暂停,STAT 变 T;再发 SIGCONT 恢复为 S:
$ kill -STOP 557; ps -o pid,stat,comm -p 557
PID STAT COMMAND
557 T sleep
$ kill -CONT 557; ps -o pid,stat,comm -p 557
PID STAT COMMAND
557 S sleep
Z(僵尸态)——僵尸的制造条件是"子进程已死而父进程未收尸"。做法:启动一个内含后台 sleep 的 bash 作为父进程并记下两个 PID,先 SIGSTOP 冻结父进程(暂停的进程无法处理 SIGCHLD 收尸),再 SIGKILL 子进程,即得僵尸:
$ bash -c 'sleep 300 & echo $! > /root/demo/ps/zchild; echo $$ > /root/demo/ps/zparent; wait' & ZW=$!; disown $ZW; sleep 1; echo ZW=$ZW
ZW=581
$ kill -STOP $(cat /root/demo/ps/zparent) && kill -9 $(cat /root/demo/ps/zchild); echo rc=$?
rc=0
$ ps -o pid,ppid,stat,comm -p $(cat /root/demo/ps/zchild),$(cat /root/demo/ps/zparent)
PID PPID STAT COMMAND
581 540 T bash
583 581 Z sleep <defunct>
583 号即僵尸:STAT=Z、命令名带 <defunct>;其父 581 号因被冻结显示 T。随后恢复父进程,它立即回收子进程,僵尸消失——最后用 ps -p 验证已查无此进程(rc=1):
$ kill -CONT $(cat /root/demo/ps/zparent); sleep 1; ps -o pid,stat,comm -p $(cat /root/demo/ps/zchild); echo rc=$?
bash: line 1: 583 Killed sleep 300
PID STAT COMMAND
rc=1
💡 说明:输出中的
bash: line 1: 583 Killed sleep 300是父进程 bash 恢复运行后报告"子进程已被 SIGKILL 终止"的作业通知,恰好证明父进程在恢复的瞬间才完成收尸——僵尸的生命周期完全由父进程掌控。
D(不可中断睡眠)——D 是磁盘 IO 等待,信号(包括 SIGKILL)都要等系统调用返回才能生效,因此它是排障时"kill -9 都杀不掉"的元凶。本环境尝试用大文件落盘(conv=fdatasync)制造 D 态,连续三次采样均只捕到 R:
$ dd if=/dev/zero of=/root/demo/ps/dd.bin bs=1M count=4000 conv=fdatasync & DD=$!; sleep 5; ps -o pid,stat,comm -p $DD
PID STAT COMMAND
593 R dd
$ ps -o pid,stat,comm -p 593
PID STAT COMMAND
593 R dd
$ sleep 5; ps -o pid,stat,comm -p 593
4000+0 records in
4000+0 records out
4194304000 bytes (4.2 GB, 3.9 GiB) copied, 12.8255 s, 327 MB/s
PID STAT COMMAND
$ kill -9 593 2>/dev/null; wait 593 2>/dev/null; echo dd-cleaned
dd-cleaned
原因如实说明:本机为高速存储且页缓存充裕,dd 的写入与同步阶段都短于采样间隔,进程始终处于 R;第三条命令执行时 dd 已自然结束(回显中带出 dd 的统计行),ps -p 输出空表。D 态在慢速磁盘、网络存储(NFS)或 IO 亚健康的机器上才容易稳定观察到,出现大面积 D 态通常意味着存储瓶颈。
s 与 +(会话组长与前台进程组)——2.1 节由 script 制造的伪终端会话在这里派上用场,三个进程的 PGID/SID/TPGID 一目了然:
$ ps -o pid,ppid,pgid,tpgid,sid,tty,stat,comm -p 563,565,566
PID PPID PGID TPGID SID TT STAT COMMAND
563 540 540 -1 540 ? S script
565 563 565 565 565 pts/0 Ss+ sh
566 565 565 565 565 pts/0 S+ sleep
解读:565 号 sh 是新会话的组长(PID=PGID=SID=565),STAT 为 Ss+——s 表示会话组长、+ 表示它是终端的前台进程组(TPGID=565 与其 PGID 一致);566 号 sleep 属于前台进程组但不是组长,STAT 为 S+;563 号 script 本身没有终端(TT=?,TPGID=-1),只有普通 S。
3.4 ps -ef(SYS V 全格式)
ps -ef 是 System V 风格的全格式:-e 全部进程 + -f 全格式(含命令行)。与 aux 相比它多了 PPID(父进程)与 C(CPU 占用整数口径),少了内存列:
$ ps -ef | head -6
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:13 ? 00:00:00 sleep infinity
tester 136 1 0 10:15 ? 00:00:00 [sleep] <defunct>
tester 139 1 0 10:15 ? 00:00:00 [sleep] <defunct>
root 155 1 0 10:15 ? 00:00:00 [sh] <defunct>
root 156 1 0 10:15 ? 00:00:00 [sleep] <defunct>
$ ps -ef | tail -10
root 557 540 0 10:21 ? 00:00:00 sleep 300
root 558 540 0 10:21 ? 00:00:00 sleep 301
root 559 540 0 10:21 ? 00:00:00 sleep 302
root 560 540 0 10:21 ? 00:00:00 sleep 303
root 561 540 0 10:21 ? 00:00:00 python3 /root/demo/ps/mt.py
root 563 540 0 10:21 ? 00:00:00 script -qc sleep 300 /dev/null
root 565 563 0 10:21 pts/0 00:00:00 sh -c sleep 300
root 566 565 0 10:21 pts/0 00:00:00 sleep 300
root 608 540 0 10:21 ? 00:00:00 ps -ef
root 609 540 0 10:21 ? 00:00:00 tail -10
各列含义:
| 列 | 含义 | 实测对照 |
|---|---|---|
| UID | 进程属主 | root / tester |
| PID | 进程 ID | — |
| PPID | 父进程 ID | 演示进程均为 540(测试脚本 bash),子 shells 层层递进 |
| C | CPU 占用(整数,%CPU/100 的旧口径) | 空闲系统基本为 0 |
| STIME | 启动时刻 | 当天为 10:21;跨天进程显示 Oct14 这类日期 |
| TTY | 控制终端 | ? / pts/0 |
| TIME | 累计 CPU 时间 | — |
| CMD | 完整命令行 | 与 aux 的 COMMAND 同源 |
PPID 列的排障价值:演示进程群 557–563 的 PPID 都是 540(测试脚本),565 的 PPID 是 563(script 派生),566 的 PPID 是 565——顺着 PPID 可以还原进程家族;而僵尸进程的 PPID 指向"该负责收尸却没收"的父进程,是 5.4 节僵尸排查的入口。
3.5 --forest 进程树(ASCII 树状视图)
--forest(BSD 短选项 f)在列表输出上追加 ASCII 连线,把 PPID 关系画成树。配合 -ef 全格式使用:
$ ps -ef --forest | tail -30
UID PID PPID C STIME TTY TIME CMD
root 540 0 0 10:21 ? 00:00:00 bash /root/ps-test.sh
root 557 540 0 10:21 ? 00:00:00 \_ sleep 300
root 558 540 0 10:21 ? 00:00:00 \_ sleep 301
root 559 540 0 10:21 ? 00:00:00 \_ sleep 302
root 560 540 0 10:21 ? 00:00:00 \_ sleep 303
root 561 540 0 10:21 ? 00:00:00 \_ python3 /root/demo/ps/mt.py
root 563 540 0 10:21 ? 00:00:00 \_ script -qc sleep 300 /dev/null
root 565 563 0 10:21 pts/0 00:00:00 | \_ sh -c sleep 300
root 566 565 0 10:21 pts/0 00:00:00 | \_ sleep 300
root 610 540 0 10:21 ? 00:00:00 \_ ps -ef --forest
root 611 540 0 10:21 ? 00:00:00 \_ tail -30
root 1 0 0 10:13 ? 00:00:00 sleep infinity
tester 136 1 0 10:15 ? 00:00:00 [sleep] <defunct>
tester 139 1 0 10:15 ? 00:00:00 [sleep] <defunct>
root 155 1 0 10:15 ? 00:00:00 [sh] <defunct>
root 156 1 0 10:15 ? 00:00:00 [sleep] <defunct>
root 212 1 0 10:15 ? 00:00:00 [sh] <defunct>
root 213 1 0 10:15 ? 00:00:00 [sleep] <defunct>
tester 327 1 0 10:17 ? 00:00:00 [sleep] <defunct>
tester 330 1 0 10:17 ? 00:00:00 [sleep] <defunct>
root 359 1 0 10:17 ? 00:00:00 [sh] <defunct>
root 360 1 0 10:17 ? 00:00:00 [sleep] <defunct>
reniceu1 507 1 0 10:20 ? 00:00:00 [sleep] <defunct>
root 535 1 0 10:20 ? 00:00:00 [bash] <defunct>
(本节回显为完整输出中与演示相关的尾部节选,实测首行为表头。)读法:\_ 表示逐层子进程,| 竖线标示上一级还有未结束的分支——540 号脚本的子进程群被逐个挂出,其中 563→565→566 一支深入了两层。树状视图最适合梳理"某个服务到底由谁派生、谁与谁同属一个守护树"。
⚠️ 注意:本例中演示脚本进程(540)与其余会话遗留进程的 PPID 显示为 0——这表示其父进程已不在当前进程视图中(由外部接入程序拉起,父进程信息对本视图不可见);常规服务器上除 1 号进程外,进程都有明确的 PPID。本机 1 号进程为
sleep infinity(系统引导流程保留的占位进程),多数标准安装的 Ubuntu 服务器上 1 号进程是 systemd。
3.6 ps axjf(作业控制视图:PGID/SID 列)
axjf = a(全部带终端)+ x(无终端)+ j(BSD 作业控制格式)+ f(树状),在树的基础上增加 PGID(进程组)、SID(会话)、TPGID(前台进程组)三列:
$ ps axjf | tail -25
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 540 540 540 ? -1 Ss 0 0:00 bash /root/ps-test.sh
540 557 540 540 ? -1 S 0 0:00 \_ sleep 300
540 558 540 540 ? -1 S 0 0:00 \_ sleep 301
540 559 540 540 ? -1 SN 0 0:00 \_ sleep 302
540 560 540 540 ? -1 S 0 0:00 \_ sleep 303
540 561 540 540 ? -1 Sl 0 0:00 \_ python3 /root/demo/ps/mt.py
540 563 540 540 ? -1 S 0 0:00 \_ script -qc sleep 300 /dev/null
563 565 565 565 pts/0 565 Ss+ 0 0:00 | \_ sh -c sleep 300
565 566 565 565 pts/0 565 S+ 0 0:00 | \_ sleep 300
540 612 540 540 ? -1 R 0 0:00 \_ ps axjf
540 613 540 540 ? -1 S 0 0:00 \_ tail -25
0 1 1 1 ? -1 Ss 0 0:00 sleep infinity
1 136 135 135 ? -1 Z 1012 0:00 [sleep] <defunct>
1 139 138 138 ? -1 Z 1012 0:00 [sleep] <defunct>
1 155 155 155 ? -1 Zs 0 0:00 [sh] <defunct>
1 156 155 155 ? -1 Z 0 0:00 [sleep] <defunct>
1 212 212 212 ? -1 Zs 0 0:00 [sh] <defunct>
1 213 212 212 ? -1 Z 0 0:00 [sleep] <defunct>
1 327 326 326 ? -1 Z 1012 0:00 [sleep] <defunct>
1 330 329 329 ? -1 Z 1012 0:00 [sleep] <defunct>
1 359 359 359 ? -1 Zs 0 0:00 [sh] <defunct>
1 360 359 359 ? -1 Z 0 0:00 [sleep] <defunct>
1 507 505 505 ? -1 ZN 1014 0:00 [sleep] <defunct>
1 535 526 526 ? -1 Z 0 0:00 [bash] <defunct>
(本节回显为完整输出中与演示相关的尾部节选。)解读三个关键关系:
- 会话:557–563 的 SID 都是 540——它们与测试脚本 bash 同属一个会话;而 565 号在自己的新会话(SID=565)里;
- 进程组:557–563 的 PGID 都是 540(同组),Ctrl+C 这类终端信号按"前台进程组"投递,同组进程会一起收到;
- TPGID:565/566 的 TPGID=565 说明前台组在 pts/0 终端上;无终端进程 TPGID 显示 -1。
排障用途:判断"kill 一个会话组长会不会连带杀掉一批子进程""哪个进程组挂在终端前台"时,axjf 比逐行看 PPID 高效得多。
3.7 线程视图:-eLf 与 -L(LWP/NLWP 列)
-L 在每行输出中附加 LWP(轻量级进程 = 线程 ID)与 NLWP(该进程的线程数),每个线程一行:
$ ps -eLf | head -8
UID PID PPID LWP C NLWP STIME TTY TIME CMD
root 1 0 1 0 1 10:13 ? 00:00:00 sleep infinity
tester 136 1 136 0 1 10:15 ? 00:00:00 [sleep] <defunct>
tester 139 1 139 0 1 10:15 ? 00:00:00 [sleep] <defunct>
root 155 1 155 0 1 10:15 ? 00:00:00 [sh] <defunct>
root 156 1 156 0 1 10:15 ? 00:00:00 [sleep] <defunct>
root 212 1 212 0 1 10:15 ? 00:00:00 [sh] <defunct>
root 213 1 213 0 1 10:15 ? 00:00:00 [sleep] <defunct>
聚焦到多线程演示进程 561(python3,主线程 + 3 个工作线程):同一 PID 下列出 4 行,LWP 分别为 561(主线程,LWP=PID)、567、568、569,NLWP=4:
$ ps -Lo pid,lwp,nlwp,stat,comm -p 561
PID LWP NLWP STAT COMMAND
561 561 4 Sl python3
561 567 4 Sl python3
561 568 4 Sl python3
561 569 4 Sl python3
对照单线程进程(本文测试脚本 bash)NLWP=1:
$ ps -o pid,nlwp,comm -p $$
PID NLWP COMMAND
540 1 bash
- LWP 即内核分配给线程的 ID(与 top -H、/proc/PID/task/ 中的 TID 同源),可以用于
top -H -p <LWP>、gdb -p等线程级排查; - STAT 的
l修饰符与 NLWP>1 一一对应; - NLWP 异常增长(如数千)常见于线程泄漏,
ps -eLo pid,nlwp,comm --sort=-nlwp | head可快速定位线程数最多的进程。
💡 技术拓展:线程实现模型——Linux 与其他系统的差异——
模型 特点 代表 1:1(内核级线程) 每个用户线程对应一个内核调度实体(LWP),可真并行 Linux NPTL、Windows N:1(用户级线程) 多个用户线程复用一个内核实体,无法利用多核 早期用户态线程库 M:N 用户线程与内核实体多对多,调度灵活但实现复杂 部分 Unix 与协程运行时 Linux 采用 1:1 模型:ps 看到的每个 LWP 都是内核独立调度的实体,
kill -9 <LWP>等价于杀线程。
3.8 -o 自定义输出列
-o/--format 允许任意拼装输出列,-e 与之组合成 ps -eo ...(先全量选择、再定制格式):
$ ps -eo pid,ppid,user,%cpu,%mem,ni,stat,comm,args | head -10
PID PPID USER %CPU %MEM NI STAT COMMAND COMMAND
1 0 root 0.0 0.0 0 Ss sleep sleep infinity
136 1 tester 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
139 1 tester 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
155 1 root 0.0 0.0 0 Zs sh <defunct> [sh] <defunct>
156 1 root 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
212 1 root 0.0 0.0 0 Zs sh <defunct> [sh] <defunct>
213 1 root 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
327 1 tester 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
330 1 tester 0.0 0.0 0 Z sleep <defunct> [sleep] <defunct>
常用说明符(完整清单见 man ps 的 STANDARD FORMAT SPECIFIERS 一节,或运行 ps L):
| 说明符 | 含义 | 与 aux/-ef 列的对应 |
|---|---|---|
| pid / ppid | 进程 ID / 父进程 ID | aux 无 ppid;-ef 有 |
| user / uid | 用户名 / 数字 UID | USER / UID |
| %cpu / %mem | CPU、内存占用率 | aux 的 %CPU、%MEM |
| ni | nice 值(-20 最高,19 最低) | aux/-ef 均无 |
| stat | 状态码 | aux 的 STAT |
| comm | 命令名(可执行文件名,仅 15 字节截断) | — |
| args | 完整命令行(别名 cmd、command) | -ef 的 CMD |
| rss / vsz | 常驻 / 虚拟内存(KB) | aux 的 RSS、VSZ |
| lstart / etime / time | 启动时刻 / 已运行时长 / 累计 CPU 时间 | aux 的 START、TIME |
实用技巧:-o 支持 列名=自定义表头(如 -o pid=进程ID,comm=命令),也支持 pid,comm= 形式的空表头。
3.9 --sort 排序
--sort 语法为 [+|-]key[,...],- 表示降序,多键用逗号分隔依次比较。按 CPU 降序取前几名:
$ ps -eo pid,comm,%cpu --sort=-%cpu | head -6
PID COMMAND %CPU
540 bash 0.2
1 sleep 0.0
136 sleep <defunct> 0.0
139 sleep <defunct> 0.0
155 sh <defunct> 0.0
按内存降序(同时带上 RSS 绝对值):
$ ps -eo pid,comm,%mem,rss --sort=-%mem | head -6
PID COMMAND %MEM RSS
561 python3 0.0 8884
540 bash 0.0 3880
622 ps 0.0 2956
563 script 0.0 1912
559 sleep 0.0 1652
按 PID 降序(看最新诞生的进程;升序直接写 --sort=pid):
$ ps -eo pid,comm --sort=-pid | head -6
PID COMMAND
625 head
624 ps
566 sleep
565 sh
563 script
-o 与 --sort 组合还能展示更精细的时间维度:STARTED(精确到秒的启动时刻)、ELAPSED(已运行时长)、TIME(累计 CPU 时间)三者对比可以一眼判断"进程跑了多久、真正用了多少 CPU":
$ ps -o pid,lstart,etime,time,comm -p 557
PID STARTED ELAPSED TIME COMMAND
557 Thu Oct 15 10:21:06 2026 00:18 00:00:00 sleep
💡 说明:%CPU 是"自进程启动以来的平均值"(CPU 时间 / 存活时长),短时突刺会被摊平;要抓实时突刺应使用 top(见参考篇)。
3.10 按值筛选:-p、-C、-u、-t、--ppid
-p 指定 PID(可逗号或空格分隔多个,且可与 -o 自由组合):
$ ps -p 1 -o pid,ppid,stat,comm,args
PID PPID STAT COMMAND COMMAND
1 0 Ss sleep sleep infinity
-C 按命令名筛选(精确匹配 comm,不带路径不带参数):
$ ps -C sleep -o pid,ppid,ni,stat,comm
PID PPID NI STAT COMMAND
1 0 0 Ss sleep
136 1 0 Z sleep <defunct>
139 1 0 Z sleep <defunct>
156 1 0 Z sleep <defunct>
213 1 0 Z sleep <defunct>
327 1 0 Z sleep <defunct>
330 1 0 Z sleep <defunct>
360 1 0 Z sleep <defunct>
507 1 10 ZN sleep <defunct>
557 540 0 S sleep
558 540 0 S sleep
559 540 10 SN sleep
560 540 0 S sleep
566 565 0 S+ sleep
-u 按用户筛选(配合 --no-headers 更适合脚本处理,见 3.12 节):
$ ps -u root -o pid,user,%mem,stat,comm --no-headers | head -8
1 root 0.0 Ss sleep
155 root 0.0 Zs sh <defunct>
156 root 0.0 Z sleep <defunct>
212 root 0.0 Zs sh <defunct>
213 root 0.0 Z sleep <defunct>
359 root 0.0 Zs sh <defunct>
360 root 0.0 Z sleep <defunct>
535 root 0.0 Z bash <defunct>
-t 按终端筛选——先取出演示终端的 tty 名,再筛选该终端上的全部进程:
$ ps -t $(ps -o tty= -p 565)
PID TTY TIME CMD
565 pts/0 00:00:00 sh
566 pts/0 00:00:00 sleep
--ppid 按父进程筛选——列出指定 PID 的全部直接子进程($$ 即当前 shell 的 PID,在本测试会话中为 540):
$ ps -o pid,ppid,stat,ni,comm --ppid $$
PID PPID STAT NI COMMAND
557 540 S 0 sleep
558 540 S 0 sleep
559 540 SN 10 sleep
560 540 S 0 sleep
561 540 Sl 0 python3
563 540 S 0 script
633 540 R 0 ps
3.11 范围筛选:-a、-x、-e 与 ax 组合
三者的语义边界(对照 ps --help all 的 Basic options 段):
| 选项 | 全称 | 选择范围 | 排除对象 |
|---|---|---|---|
-a | all with tty, except session leaders | 所有有控制终端的进程 | 会话组长 |
-x | processes without controlling ttys | 无控制终端的进程(本用户) | 有终端的进程 |
-e/-A | all processes | 全系统所有进程 | 无 |
实测 -a:全机唯一带终端的进程是演示会话里的 sleep(其组长 sh 被排除,印证"except session leaders"):
$ ps -a
PID TTY TIME CMD
566 pts/0 00:00:00 sleep
实测 -x:无终端的本用户进程(注意其输出版式变化——BSD 风格下附带了 STAT 列):
$ ps -x | head -10
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 sleep infinity
155 ? Zs 0:00 [sh] <defunct>
156 ? Z 0:00 [sleep] <defunct>
212 ? Zs 0:00 [sh] <defunct>
213 ? Z 0:00 [sleep] <defunct>
359 ? Zs 0:00 [sh] <defunct>
360 ? Z 0:00 [sleep] <defunct>
535 ? Z 0:00 [bash] <defunct>
540 ? Ss 0:00 bash /root/ps-test.sh
ax = -a 的范围 ∪ -x 的范围(但 -a 部分含其他用户进程),也就是 aux 的选择器:
$ ps ax | head -10
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 sleep infinity
136 ? Z 0:00 [sleep] <defunct>
139 ? Z 0:00 [sleep] <defunct>
155 ? Zs 0:00 [sh] <defunct>
156 ? Z 0:00 [sleep] <defunct>
212 ? Zs 0:00 [sh] <defunct>
213 ? Z 0:00 [sleep] <defunct>
327 ? Z 0:00 [sleep] <defunct>
330 ? Z 0:00 [sleep] <defunct>
-e 是最彻底的全量视图,行数即系统进程总数(配合 wc -l 可计数):
$ ps -e --no-headers | wc -l
24
$ ps -e | wc -l
25
3.12 --no-headers(去掉表头行)
--no-headers 抑制表头输出,上一节的两组 wc -l 计数(24 与 25,相差恰为 1 行表头)即是它的直接验证。它还让输出"干净可管道化",常与 awk 配合提取单列,或让 ps -u root -o ... --no-headers 的结果直接喂给脚本循环:
$ ps -u root -o pid,user,%mem,stat,comm --no-headers | head -8
1 root 0.0 Ss sleep
155 root 0.0 Zs sh <defunct>
156 root 0.0 Z sleep <defunct>
212 root 0.0 Zs sh <defunct>
213 root 0.0 Z sleep <defunct>
359 root 0.0 Zs sh <defunct>
360 root 0.0 Z sleep <defunct>
535 root 0.0 Z bash <defunct>
3.13 ps aux | grep 自身陷阱与 [g]rep 技巧
最常见的"找进程"写法是 ps aux | grep 关键字,但它有一个经典陷阱:grep 进程自己的命令行也包含关键字,于是结果里总多出一行 grep。实测(注意最后一行 644 grep sleep):
$ ps aux | grep sleep
root 1 0.0 0.0 2792 1464 ? Ss 10:13 0:00 sleep infinity
tester 136 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 139 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 156 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 213 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 327 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
tester 330 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
root 360 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
reniceu1 507 0.0 0.0 0 0 ? ZN 10:20 0:00 [sleep] <defunct>
root 557 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 300
root 558 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 301
root 559 0.0 0.0 2792 1652 ? SN 10:21 0:00 sleep 302
root 560 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 303
root 563 0.0 0.0 2808 1912 ? S 10:21 0:00 script -qc sleep 300 /dev/null
root 565 0.0 0.0 2892 1712 pts/0 Ss+ 10:21 0:00 sh -c sleep 300
root 566 0.0 0.0 2792 1688 pts/0 S+ 10:21 0:00 sleep 300
root 644 0.0 0.0 3472 1740 ? S 10:21 0:00 grep sleep
技巧:把关键字的一个字符改写为字符集 [s]——正则 [s]leep 依然匹配 sleep 字样,但 grep 自身命令行里的字面量是 [s]leep,不匹配 [s]leep 这个正则,于是自己被排除:
$ ps aux | grep [s]leep
root 1 0.0 0.0 2792 1464 ? Ss 10:13 0:00 sleep infinity
tester 136 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 139 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 156 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
root 213 0.0 0.0 0 0 ? Z 10:15 0:00 [sleep] <defunct>
tester 327 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
tester 330 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
root 360 0.0 0.0 0 0 ? Z 10:17 0:00 [sleep] <defunct>
reniceu1 507 0.0 0.0 0 0 ? ZN 10:20 0:00 [sleep] <defunct>
root 557 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 300
root 558 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 301
root 559 0.0 0.0 2792 1652 ? SN 10:21 0:00 sleep 302
root 560 0.0 0.0 2792 1524 ? S 10:21 0:00 sleep 303
root 563 0.0 0.0 2808 1912 ? S 10:21 0:00 script -qc sleep 300 /dev/null
root 565 0.0 0.0 2892 1712 pts/0 Ss+ 10:21 0:00 sh -c sleep 300
root 566 0.0 0.0 2792 1688 pts/0 S+ 10:21 0:00 sleep 300
两相对比:第一次多出的 644 grep sleep 一行消失了。更规范的替代是 ps -C <命令名>(3.10 节,按 comm 精确匹配)与同包的 pgrep(按模式返回 PID 列表),grep 写法仅在需要匹配完整命令行参数时仍有优势。
3.14 ps 与 /proc/PID/ 的对应
ps 的每一列都源自内核暴露的 /proc/<PID>/ 伪文件系统,理解这层对应后,ps 看不到的信息可以直接去 /proc 里挖。先看 ps 行:
$ ps -p 557 -o pid,ppid,stat,user,comm
对应 /proc/557/status 的关键字段(Name、State、Tgid/Pid、PPid、Uid/Gid、Threads 与 ps 的 comm、STAT、PID、PPID、user、NLWP 一一对应):
$ cat /proc/557/status | head -11
Name: sleep
Umask: 0022
State: S (sleeping)
Tgid: 557
Ngid: 0
Pid: 557
PPid: 540
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
$ grep -E '^(Name|State|Tgid|Pid|PPid|Threads|Uid|Gid)' /proc/557/status
Name: sleep
State: S (sleeping)
Tgid: 557
Pid: 557
PPid: 540
Uid: 0 0 0 0
Gid: 0 0 0 0
Threads: 1
cmdline 文件保存完整命令行,参数之间以 \0 分隔(与 ps 的 args 列同源),用 tr 把 \0 换成空格即还原:
$ tr '\0' ' ' < /proc/557/cmdline; echo
sleep 300
再看 1 号进程的 status,字段结构与普通进程完全一致(本机 1 号进程为系统引导流程启动的 sleep;多数标准 Ubuntu 服务器上是 systemd,即 Name: systemd):
$ cat /proc/1/status | head -11
Name: sleep
Umask: 0022
State: S (sleeping)
Tgid: 1
Ngid: 0
Pid: 1
PPid: 0
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 64
$ ps -p 1 -o pid,ppid,stat,comm,args
PID PPID STAT COMMAND COMMAND
1 0 Ss sleep sleep infinity
字段映射速查:
| /proc/PID/status 字段 | ps 对应 | 备注 |
|---|---|---|
| Name | comm | 可执行文件名(内核 15 字节截断) |
| State | STAT 主码 | S (sleeping) ↔ S |
| Tgid / Pid | PID | 线程组 ID / 线程 ID |
| PPid | PPID | 僵尸排查的入口 |
| Threads | NLWP | -L 显示的线程数 |
| Uid / Gid | user / UID | 四个数分别是 real/effective/saved/fs |
💡 说明:status 只读,反映进程元数据;同名目录下的
stat(单行机器格式,top 等工具的数据源)、cmdline、environ、fd/(打开的文件描述符)、task/(各线程子目录)也是排障常客。
3.15 报错行为与退出码
查不存在的 PID:输出仅有表头,退出码 1——脚本里常用 ps -p $PID >/dev/null 判断进程是否存活:
$ ps -p 999999; echo rc=$?
PID TTY TIME CMD
rc=1
无效的排序键:直接报错并退出(rc=1),提示 unknown sort specifier 后附用法:
$ ps --sort=bogus; echo rc=$?
error: unknown sort specifier
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
rc=1
无效的输出说明符:同样报错退出(rc=1):
$ ps -o bogus; echo rc=$?
error: unknown user-defined format specifier "bogus"
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
rc=1
负 nice 值权限错误:nice -n -5 试图提高优先级,即使 root 也被拒绝(受系统能力策略限制),nice 打印警告后仍以普通优先级执行目标命令,因此退出码是 0——这正是 3.3 节 560 号进程 STAT 为 S 而非 S< 的原因:
$ nice -n -5 true; echo rc=$?
nice: cannot set niceness: Permission denied
rc=0
ps -aux 混写风格:在不存在名为 aux 的用户时,procps-ng 3.3.17 会自动回退为 BSD 风格解释,输出与 ps aux 相同(rc=0)。但要警惕:-u 在 UNIX 风格中是"按用户筛选",一旦系统中恰好存在同名用户,ps -aux 就变成了"筛选 aux 用户的进程",语义完全改变。书写时请保持风格统一:要么 ps aux,要么 ps -ef,不要混写 -aux:
$ ps -aux | head -3; echo rc=$?
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2792 1464 ? Ss 10:22 0:00 sleep infinity
tester 28 0.0 0.0 0 0 ? Zs 10:22 0:00 [sleep] <defunct>
rc=0
实测结束后的清理动作(杀掉演示进程并用 ps -p 验证已全部退出,rc=1 表示查无进程):
$ kill 557 558 559 560 561 2>/dev/null; wait 557 558 559 560 561 2>/dev/null; echo cleaned
cleaned
$ kill 566 565 2>/dev/null; sleep 0.5; kill 563 2>/dev/null; wait 563 2>/dev/null; echo cleaned
cleaned
$ ps -o pid,stat,comm -p 557,558,559,560,561,563; echo rc=$?
PID STAT COMMAND
rc=1
四、实战示例
以下组合命令均在同一环境实测通过(本节为独立演示会话,PID 与 2.1 节不同属正常)。
4.1 排查 CPU 占用最高的进程
先启动一个多线程演示进程,然后按 %CPU 降序取前几名——ps -eo + --sort=-%cpu + head 是没有 top 时的标准做法:
$ mkdir -p /root/demo/ps && python3 /root/demo/ps/mt.py & PY=$!; sleep 1; echo PY=$PY
PY=143
$ ps -eo pid,ppid,user,%cpu,%mem,stat,comm --sort=-%cpu | head -6
PID PPID USER %CPU %MEM STAT COMMAND
137 0 root 2.0 0.0 Ss bash
146 143 root 1.0 0.0 Sl python3
1 0 root 0.0 0.0 Ss sleep
28 1 tester 0.0 0.0 Zs sleep <defunct>
29 1 tester 0.0 0.0 Zs sleep <defunct>
4.2 排查内存占用最高的进程
同一思路按 %MEM 降序,附 RSS 绝对值更直观:
$ ps -eo pid,ppid,user,%mem,rss,comm --sort=-%mem | head -6
PID PPID USER %MEM RSS COMMAND
146 143 root 0.0 8964 python3
137 0 root 0.0 3392 bash
152 137 root 0.0 3020 ps
143 137 root 0.0 1796 bash
1 0 root 0.0 1464 sleep
4.3 单用户视角的进程总览(含运行时长)
-u 限定用户 + -o 定制列 + --sort=-%mem:一次看清该用户最耗资源的进程及运行时长:
$ ps -u root -o pid,ppid,%cpu,%mem,stat,etime,comm --sort=-%mem | head -8
PID PPID %CPU %MEM STAT ELAPSED COMMAND
146 143 1.0 0.0 Sl 00:01 python3
137 0 2.0 0.0 Ss 00:01 bash
154 137 0.0 0.0 R 00:00 ps
143 137 0.0 0.0 S 00:01 bash
155 137 0.0 0.0 S 00:00 head
1 0 0.0 0.0 Ss 04:02 sleep
55 1 0.0 0.0 Zs 04:00 sh <defunct>
4.4 僵尸进程批量排查
僵尸的特征是命令行带 <defunct>(STAT=Z),用 [d]efunct 字符集技巧避免 grep 自身混入(原理见 3.13 节):
$ ps aux | grep [d]efunct | head -8
tester 28 0.0 0.0 0 0 ? Zs 10:22 0:00 [sleep] <defunct>
tester 29 0.0 0.0 0 0 ? Zs 10:22 0:00 [sleep] <defunct>
root 55 0.0 0.0 0 0 ? Zs 10:22 0:00 [sh] <defunct>
root 56 0.0 0.0 0 0 ? Z 10:22 0:00 [sleep] <defunct>
拿到僵尸清单后,按 PPID 列找到父进程,让父进程正常退出或对其发起收尸,僵尸才会消失(直接 kill 僵尸无效,见 5.4)。
4.5 按完整命令行精确定位进程
args 列 + [m]t.py 技巧:当 comm 只有 python3 无法区分多个 Python 进程时,按命令行参数定位:
$ ps -eo pid,user,args | grep [m]t.py
146 root python3 /root/demo/ps/mt.py
4.6 查看最近启动的进程
--sort=start_time(升序)配合 tail:排查"刚部署的服务起来没有、顺序对不对":
$ ps -eo pid,user,args --sort=start_time | tail -6
56 root [sleep] <defunct>
137 root bash /root/ps-n.sh
143 root bash /root/ps-n.sh
146 root python3 /root/demo/ps/mt.py
161 root ps -eo pid,user,args --sort=start_time
162 root tail -6
五、注意事项与常见问题
Q1:ps aux | grep 找进程,结果里总有一行 grep 自己,怎么去掉?
用 [g]rep 字符集技巧(ps aux | grep [s]leep),或改用 ps -C 命令名、pgrep -f 模式。前者原理见 3.13 节实测;pgrep 由同一个 procps-ng 包提供,按模式返回纯 PID 列表,天然不含自身。
Q2:ps 的 %CPU 加起来怎么会超过 100%?
ps 的 %CPU 以单核 100% 为基准,多核机器上多线程进程可以超过 100%(8 核跑满即 800%)。本机 12 核,理论上限 1200%。这与 top 进程区的 %CPU 口径一致;而 top 头部 %Cpu(s) 行的 100% 是全部核心合计后的百分比,两处数字不可直接相加比较。
Q3:STAT 里的组合码怎么读?
先读主码再看修饰符,如 Ss+ = 可中断睡眠 + 会话组长 + 前台进程组;SN = 睡眠 + 低优先级;Zs = 僵尸 + (生前是)会话组长;Sl = 睡眠 + 多线程。全部组合含义见 3.3 节码表。
Q4:僵尸进程 kill -9 也杀不掉,怎么办? 僵尸已经死亡,只剩一条退出状态记录,对它发任何信号都无意义。唯一解法是让 PPID 指向的父进程完成收尸:父进程还在运行就等它 wait,或向父进程发送 SIGCHLD;父进程卡死就重启/杀掉父进程,僵尸将被 1 号进程接管回收。注意若 1 号进程本身从不 wait(本环境即如此),僵尸会一直留在进程表里,但只占用一个 PID 条目,不消耗内存与 CPU。实测复现与收尸过程见 3.3 节 Z 段。
Q5:为什么 root 也可能无法设置负 nice 值?
提高优先级(负 nice)需要内核能力层面的授权,被裁剪或加固过的系统即使 root 也可能收到 nice: cannot set niceness: Permission denied(实测见 3.15 节)。常规服务器上 root 可以设置;普通用户只能调低优先级(正 nice)。
Q6:ps、top、pgrep 三者怎么分工? 同属 procps-ng 的三个工具定位不同:
| 工具 | 形态 | 优势 | 典型场景 |
|---|---|---|---|
| ps | 一次性快照 | 列可定制、可排序、可管道、可脚本化 | 巡检取数、写监控脚本、定位进程 |
| top | 全屏实时刷新 | 持续观察动态趋势、交互排序/杀进程 | 现场排障、盯着资源变化 |
| pgrep | 只输出 PID | 精简、安全(不混淆自身) | 脚本中判断/获取目标进程 PID |
Q7:D 状态进程为什么连 kill -9 都杀不死? D 是不可中断睡眠:进程阻塞在内核系统调用(典型为磁盘 IO)中,信号只能在系统调用返回后被处理。SIGKILL 只是把进程标记为待终止,它必须等 IO 返回。遇到大面积 D 态应排查存储链路(磁盘故障、NFS 卡死、IO 打满),而不是反复 kill。
Q8:ps -aux 和 ps aux 是一回事吗?
多数情况下输出相同(无名为 aux 的用户时自动回退,实测见 3.15 节),但 -u 的 UNIX 语义是"按用户筛选",一旦系统中存在名为 aux 的用户,ps -aux 的含义就变成"筛选 aux 用户",结果完全不同。同样地,ps -p(筛选 PID)与 BSD 的 p 也存在此类差异——建议风格统一,BSD 形式不加 -。
Q9:START 列有时是时间有时是日期?
当天启动的进程显示 HH:MM:SS(或 10:21),超过 24 小时的进程显示日期(如 Oct14),超过一年的显示年份。需要精确时刻用 -o lstart(实测见 3.9 节)。
Q10:ps 看到的进程数和监控系统的对不上?
先确认选择器:ps(默认)只看当前用户当前终端,ps aux/ps -e 才是全量(范围差异实测见 3.11 节);再确认统计口径是否排除了内核线程与僵尸。
六、参考链接
- ps(1) 手册(man7.org):https://man7.org/linux/man-pages/man1/ps.1.html
- proc(5) 伪文件系统手册(/proc/PID/status 等):https://man7.org/linux/man-pages/man5/proc.5.html
- Ubuntu 22.04 (jammy) ps 在线手册页:https://manpages.ubuntu.com/manpages/jammy/en/man1/ps.1.html
- procps-ng 源码仓库:https://gitlab.com/procps-ng/procps
- procps-ng 上游发布页(含版本说明):http://procps.sourceforge.net/
- 同系列实操篇:《top 命令详解与参数实测(Ubuntu 22.04)》