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 陷阱,全部命令与回显真实可复现。

Linux命令Linux系统UbuntuUbuntu 22.04命令行进程管理2026/10/15

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 -m total 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 -f
BSD不带 -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进程 ID1(1 号进程)、557 等
%CPUCPU 占用率,单核 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) 手册,主状态码如下:

主码名称含义
RRunning正在运行或在运行队列中等待 CPU
SSleeping可中断睡眠,等待事件(IO、信号、定时器)
DUninterruptible sleep不可中断睡眠,通常是磁盘 IO,信号无法唤醒
ZZombie僵尸:进程已终止,父进程尚未读取其退出状态
TStopped被信号(SIGSTOP/SIGTSTP)暂停,或正被调试器跟踪
IIdle空闲的内核线程

常用修饰符如下(均可组合出现):

修饰符含义
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 层层递进
CCPU 占用(整数,%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 / 父进程 IDaux 无 ppid;-ef 有
user / uid用户名 / 数字 UIDUSER / UID
%cpu / %memCPU、内存占用率aux 的 %CPU、%MEM
ninice 值(-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 段):

选项全称选择范围排除对象
-aall with tty, except session leaders所有有控制终端的进程会话组长
-xprocesses without controlling ttys无控制终端的进程(本用户)有终端的进程
-e/-Aall 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 对应备注
Namecomm可执行文件名(内核 15 字节截断)
StateSTAT 主码S (sleeping) ↔ S
Tgid / PidPID线程组 ID / 线程 ID
PPidPPID僵尸排查的入口
ThreadsNLWP-L 显示的线程数
Uid / Giduser / 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 节);再确认统计口径是否排除了内核线程与僵尸。

六、参考链接