ps —— 看进程
一句话定义
ps(process status)显示当前时刻的进程快照——和 top 实时刷新不同,ps 是"这一瞬间的列表"。它有两套语法体系(BSD 和 SysV)让人混乱,但实战只需要记住两三种调用。
典型场景
- 找进程 PID:
ps aux | grep nginx - 看进程树:
ps auxf或pstree - 看 K8s 容器进程:
ps -ef | grep containerd-shim - 自定义列:
ps -eo pid,user,cmd - 找占内存最多的 10 个:
ps aux --sort=-%mem | head
两套语法
ps aux # BSD 风格(无前导 -)
ps -ef # SysV 风格(带 -)
ps -A # POSIX
两种都常见、各人偏好不同。记住 ps aux 一个就够——它显示得最全。
| 语法 | 列 |
|---|---|
ps aux | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND |
ps -ef | UID PID PPID C STIME TTY TIME CMD |
ps aux 输出详解
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 167544 11920 ? Ss May27 0:02 /sbin/init
root 1234 0.5 1.2 45000 8000 ? Ssl 10:23 0:15 /usr/sbin/sshd -D
root 5678 0.0 0.3 12000 3000 ? Ss 11:05 0:00 /usr/sbin/cron
nginx 8901 0.2 0.5 80000 15000 ? S 12:30 0:05 nginx: worker process
| 列 | 含义 |
|---|---|
USER | 进程的 effective user |
PID | 进程 ID |
%CPU | CPU 占比(注意:自进程启动以来的平均值) |
%MEM | 物理内存占比 |
VSZ | 虚拟内存大小(KB),含 mmap 但未必真实占用 |
RSS | 常驻物理内存(KB),这才是真用了多少 |
TTY | 关联的终端(? = 没有终端,daemon) |
STAT | 进程状态(见下表) |
START | 启动时间 |
TIME | 累计 CPU 时间 |
COMMAND | 命令行(截断,要完整加 -w 或 -f) |
STAT 列详解
| 字符 | 含义 |
|---|---|
R | Running 或可运行 |
S | Sleeping(可中断的睡眠,绝大多数进程) |
D | Uninterruptible sleep(通常是 I/O 等待,kill 不掉) |
Z | Zombie(僵尸,已 exit 但父进程没 wait) |
T | 停止(SIGSTOP / 调试中) |
+ | 前台进程组 |
s | 会话首进程(session leader) |
l | 多线程 |
< | 高优先级(nice 负值) |
N | 低优先级(nice 正值) |
D 状态是排错警钟——通常是磁盘 I/O 卡住、NFS 挂死、内核 bug。kill -9 也杀不掉 D 状态进程,要解决根因(释放 I/O)。
--sort 排序(实战最爱)
ps aux --sort=-%cpu | head # CPU 占用 top 10
ps aux --sort=-%mem | head # 内存占用 top 10
ps aux --sort=-rss | head # 按 RSS 真实内存排
ps aux --sort=-time | head # 累计 CPU 时间最长(运行时间最久的工作进程)
- 前缀表示降序,无前缀升序。
K8s 节点上找"哪个 pod 在烧 CPU":
ssh m4 'ps aux --sort=-%cpu | head -20'
# 看 COMMAND 列,配合 crictl ps 找到对应容器
自定义列 -o
ps -eo pid,user,%cpu,%mem,rss,comm --sort=-%cpu | head
# PID USER %CPU %MEM RSS COMMAND
# 1234 root 5.2 1.0 8000 nginx
常用列:
| 列名 | 含义 |
|---|---|
pid | PID |
ppid | 父 PID |
pgid | 进程组 ID |
sid | 会话 ID |
user | effective user |
ruser | real user |
uid | UID |
comm | 命令(短,不含参数) |
args | 完整命令(含参数) |
cmd | 同 args |
%cpu | CPU 占比 |
%mem | 内存占比 |
rss | RSS(KB) |
vsz | 虚拟内存 |
stat | 状态 |
start / start_time | 启动时间 |
time | 累计 CPU 时间 |
etime | 进程已运行多久(elapsed) |
lstart | 启动时间(完整格式) |
tty | 终端 |
nice | nice 值 |
psr | 当前在哪个 CPU 核上 |
wchan | 内核等待原因 |
cgroup | 所属 cgroup |
K8s 排错常用:
ps -eo pid,etime,user,%cpu,%mem,cgroup,args --sort=-%mem | head
# 看 cgroup 列能直接看出"这个进程属于哪个 K8s pod"
# cgroup 名形如 /kubepods/burstable/pod-xxx/abc...
进程树
ps auxf # f = forest
# 用缩进显示父子关系
pstree # 更直观的树
pstree -p # 加 PID
pstree -p 1234 # 看 PID 1234 的子树
K8s 节点上:
systemd
├─ containerd
│ ├─ containerd-shim
│ │ ├─ pause ← pod sandbox
│ │ └─ nginx ─ nginx worker ← 应用容器
│ └─ containerd-shim
│ ├─ pause
│ └─ python ─ uvicorn ─ python ...
└─ kubelet
排查"这个进程是哪个 pod 的":从进程往上追到 containerd-shim,再用 crictl 反查。
找进程的几个常见方式
1. ps aux | grep(最普及,有"自我匹配"陷阱)
$ ps aux | grep nginx
root 1234 ... nginx: master
root 1235 ... nginx: worker
root 5678 ... grep --color=auto nginx ← grep 自己也被匹配
避免自我匹配:
ps aux | grep "[n]ginx" # 模式 [n]ginx 实际匹配 nginx
# 但 ps 里 grep 的命令行是 grep [n]ginx,不匹配
2. pgrep / pkill(更现代)
pgrep nginx # 只输出 PID
pgrep -a nginx # 输出 PID + 命令行
pgrep -u root nginx # 限定 user
pgrep -P 1234 # 按 PPID 找(找子进程)
pkill nginx # 杀
pkill -9 nginx # SIGKILL
pkill -u alice # 杀某用户所有进程(小心)
写脚本里首选 pgrep,比 ps | grep 更可靠。
3. pidof(按精确进程名)
pidof nginx
# 1234 1235
只匹配命令名,不匹配参数。比 pgrep 更严格。
看进程详情:/proc/<PID>/
ps 信息源都来自 /proc/<PID>/。直接看更详细:
ls /proc/1234/
# cmdline cwd environ exe fd/ limits maps mountinfo ns/ root status ...
cat /proc/1234/cmdline | tr '\0' ' ' # 完整命令行(参数用 \0 分隔,转空格)
cat /proc/1234/status # 详细状态(含线程数 / 内存 / capabilities)
cat /proc/1234/limits # ulimit
cat /proc/1234/cgroup # 所属 cgroup
ls /proc/1234/fd/ # 打开的所有文件描述符
readlink /proc/1234/exe # 可执行文件路径
readlink /proc/1234/cwd # 当前工作目录
ls /proc/1234/ns/ # namespace
K8s 排错常用:
# 找 pod 容器的 PID
PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
# 看它的 cgroup
cat /proc/$PID/cgroup
# 12:cpuset:/kubepods/burstable/pod-xxx/...
# 看它的 namespaces
ls -l /proc/$PID/ns/
# 跟节点 root 的 /proc/1/ns/ 比,能看出哪些 ns 隔离了
看线程
ps -eLf | grep java # -L 显示线程
ps -eo pid,nlwp,comm | sort -k2 -rn | head # 按线程数排序
ps -T -p 1234 # 看进程 1234 的所有线程
# PID SPID TTY TIME CMD
# 1234 1234 ... java
# 1234 1235 ... java
# 1234 1236 ... java
Java 应用排查"哪个线程在烧 CPU":
top -H -p 1234 # top 的线程模式
# 看到 TID 烧 CPU,转 16 进制 → jstack 里查
等价命令的实战速查
# 1. 看全部进程
ps aux
ps -ef
ps -A
ps -e
# 2. 看自己的进程
ps # 默认只看当前 shell 的
ps x # 看自己所有
# 3. 按 PID
ps -p 1234,5678 # 多个 PID
ps -p 1234 -o pid,user,cmd
# 4. 按 user
ps -u nginx # 按 effective user
ps -U nginx # 按 real user
# 5. 按 cgroup(K8s 视角)
ps -eo pid,cgroup,cmd | grep kube-system
# 找 kube-system pod 的进程
训练营实战
1. 节点 CPU 突然飙高
top # 大致看到哪个进程
# 但是 top 切到的话用 ps:
ps aux --sort=-%cpu | head -10
# 拿到 PID
# 进一步
top -H -p <PID> # 看是哪个线程
cat /proc/<PID>/cgroup # 确定哪个 K8s pod
2. 找占内存最大的 pod
ps -eo pid,rss,cgroup,comm --sort=-rss | head -20
# RSS 是 KB,要 MB 自己 / 1024
# 或者更整洁
ps -eo pid,rss,comm --sort=-rss | awk '$2 > 100000 {printf "%-8s %8d MB %s\n", $1, $2/1024, $3}' | head
3. 找 zombie
ps aux | awk '$8 ~ /Z/' # STAT 列含 Z
# 或者
ps -eo stat,pid,ppid,cmd | awk '$1 ~ /Z/'
zombie 都需要父进程 wait 才能彻底清。父进程挂了 zombie 被 init (PID 1) 接管自动清。
K8s pod 里 zombie 堆积通常是因为容器主进程没正确 reap 子进程。修法:用 --init 或者多进程容器用 tini / dumb-init。
4. 找一个进程的容器 / pod
PID=12345
cat /proc/$PID/cgroup
# 12:cpu,cpuacct:/kubepods/burstable/pod-d4e6a7-...
# 提取 pod UID 反查 K8s
POD_UID=$(grep -oE 'pod[a-f0-9-]+' /proc/$PID/cgroup | head -1 | sed 's/pod//;s/_/-/g')
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.uid} {.metadata.namespace}/{.metadata.name}{"\n"}{end}' \
| grep $POD_UID
5. 长跑进程(uptime)
ps -eo pid,etime,cmd --sort=-etime | head
# etime 显示进程运行了多久([[DD-]hh:]mm:ss)
# 跑了多久的 sshd?
ps -eo pid,lstart,cmd | grep sshd
# 看 lstart 列得到精确启动时间戳
常见踩坑
坑 1:%CPU 是平均值,不是当前
ps aux | grep my-app
# %CPU 显示 50%
ps aux 的 %CPU 是进程启动以来的累计 CPU 时间 / 进程运行时长。一个跑了一周、最近 1 小时狂吃 CPU 的进程,%CPU 可能只显示 5%——因为平均下来不高。
要看"当前瞬时 CPU"用 top(或 top -b -n 1)。
坑 2:RSS 比"实际占用"高
RSS 是常驻内存(含共享页),多个进程共享同一 libc 的话,每个进程 RSS 都把 libc 算上。所以所有进程 RSS 之和 > 系统总内存 是正常的。
要看进程独占内存:PSS(proportional set size)—— 共享页按比例分摊。
ps -eo pid,rss,comm | head
# 没 PSS
cat /proc/<PID>/smaps_rollup | grep -E 'Pss|Rss|Uss'
# Rss: 50000 kB
# Pss: 30000 kB ← 这才是"按比例分摊的实际占用"
或者用 smem 工具直接看 PSS。
坑 3:ps aux 截断长命令
ps aux | grep python
# python ...
# 后面参数被截掉,看不出哪个 python 进程
加 -w / -ww 或 f:
ps auxw # 不截断
ps auxww # 完全不截断
ps -eo pid,args # args 列默认不截断
或者读 /proc/<PID>/cmdline。
坑 4:USER 显示 1000 而不是用户名
ps -eo pid,user,cmd
# 1234 1000 python ← UID 没解析成名字
UID 在 /etc/passwd 里没条目(容器里很常见,UID 1000 没用户名)。要看名字加 --no-headers 或自定义:
ps -eo pid,user:20,cmd # user 列展宽
容器里看不到名字是正常的——pod 的 UID 在节点 host /etc/passwd 里没条目。
坑 5:用 ps -ef | grep 也匹配自己
同 grep 的坑:
ps -ef | grep nginx
# ... grep --color=auto nginx ← 自己
用 [n]ginx 或 pgrep。
坑 6:D 状态进程 kill 不掉
kill -9 1234
# 进程还在
ps aux | grep 1234
# STAT 还是 D
D 状态在内核等 I/O。SIGKILL 也杀不掉——必须等内核把 I/O 完成或失败。
判断 D 原因:
cat /proc/1234/wchan # 看在等什么内核函数
# nfs_wait_on_request ← NFS 卡了
# io_schedule ← 磁盘 I/O
NFS / iSCSI 网络存储卡死是 D 状态的常见来源。
坑 7:ps 输出在不同发行版列宽不同
ps aux # Ubuntu 上 PID 4 位、CentOS 上 6 位
写脚本解析时不要按列宽硬切,用 awk 按字段:
ps aux | awk '{print $2, $11}' # 第 2 字段 PID、第 11+ 字段 COMMAND
但是 COMMAND 含空格 → awk 不能简单 $11。要用:
ps -eo pid,comm,args
# 或者用 ps -o 显式指定列