Linux ps命令详解:从进程快照到故障排查实战
在Linux上折腾久了你会发现ps是使用频率最高、但又最容易被低估的一条命令。说它高频是因为几乎每次排查问题都得敲一下说它被低估是因为大部分人只会ps aux看一眼输出真要解释每一列的含义、STAT 状态码的区别、怎么排序怎么过滤能说全的人并不多。我见过不少干了几年运维的同事看到 D 状态和 Z 状态就慌遇到 TIME 和 START 分不清更不用说在脚本里用-o自定义输出去做监控采集了。这篇不打算给你罗列一百个参数那没有意义。我按自己实际工作的习惯把ps命令讲透从它的原理和定位开始到ps aux、ps -ef这类常见用法的区别再到输出字段逐一拆解、STAT 状态码解读、排序过滤的骚操作最后给几个排查场景和踩坑实录。不管你是刚入门 Linux 的新手还是想查漏补缺的老手这篇应该都能让你对ps有个完整的认识。看的过程中建议你打开终端跟着敲光看是记不住的。1. 先搞清楚 ps 的定位它是系统的“进程快照机”1.1 ps 不是 top它是拍照片的不是直播的ps全称是 Process Status作用就是展示当前系统里进程的状态。它和top、htop最大的区别在于top是动态刷新的界面上数据一直在变而ps执行一次就输出一次相当于拍了一张进程快照。所以你要是想“持续观察”某个进程的 CPU 变化用ps就不合适应该用top或者pidstat。ps的输出数据来自哪里答案是/proc文件系统。Linux 里每一个运行中的进程在/proc下都有一个以数字命名的目录数字就是 PID。目录里有cmdline、status、stat、environ这些文件里面记录着进程的命令行参数、状态、占用资源等原始信息。你可以自己验证一下cat /proc/1/comm如果系统是 systemd 的话一般会输出systemd。这和执行ps -p 1 -o comm得到的结果一模一样。理解了这一点你就明白ps本身不神秘它就是/proc的展示层。真正原始的数据在/proc里ps只是负责把它们聚合、格式化、按你指定的方式打印出来。这也是为什么有时候容器里连ps都没有你还能通过cat /proc/1/comm大概猜出 1 号进程是什么。1.2 ps 命令能帮你回答哪些问题把ps用好了很多排查场景都能快速定位当前系统上有哪些进程在跑它们属于哪个用户哪个进程占 CPU 高哪个进程占内存多进程是正常睡眠、正在运行还是已经变成僵尸进程启动多久了父进程是谁有没有线程数量异常进程的可执行文件路径和完整启动参数是什么。这些都是日常运维、故障排查、甚至面试题里的高频场景。尤其是那些“服务器突然变卡”“CPU 飙到 100%”“内存快满了但又不知道谁占的”这类问题ps往往是你敲的第一条命令。我自己的习惯是遇到机器异常先ps aux --sort-%cpu | head -20看一眼再用top去确认基本就能圈定嫌疑人。2. 最常用的几种用法ps aux 和 ps -ef 到底怎么选2.1 不带参数你现在执行的 shell 直属进程直接敲一个裸的psps输出长这样PID TTY TIME CMD 12345 pts/0 00:00:00 bash 23456 pts/0 00:00:00 ps它只显示当前终端会话里的进程严格来说是你当前 shell 的进程和刚刚敲出来的ps自己。注意这里没有 USER、%CPU、%MEM 这些列。裸的ps在实际工作中基本用不上但理解了它你再看后面各种参数组合就不会晕因为很多参数的意义就是“扩大展示范围”和“更多字段”。好比你拍照时先决定镜头对准哪里选进程范围再决定要不要开美颜选输出字段。2.2 ps aux最经典的 BSD 风格组合ps aux是无数教程里最爱教的命令。拆开看a表示显示所有终端相关的进程包括其他用户的进程u表示以用户为主的详细格式输出会显示 USER、%CPU、%MEM 这些列x表示同时显示没有控制终端的进程。三个合起来意思就是“把系统里所有进程的快照都给我而且用详细格式”。有了x你才能看到那些守护进程、后台服务、内核线程比如 nginx、sshd、dockerd 这些它们的 TTY 列通常是?。ps aux的输出列大致是USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND这里要提一个很多人会犯的迷糊ps aux里的u和参数-u不是一回事。-u是用来指定用户的比如ps -u root是只看 root 的进程。两个u含义完全不同别记混了。2.3 ps -efSystem V 风格脚本取 PPID 更好用再看ps -ef-e表示显示所有进程-f表示全格式输出。输出列是UID PID PPID C STIME TTY TIME CMD对比ps aux你发现它多了 PPID父进程 ID、CCPU 利用率、STIME启动时间但同时没有%CPU、%MEM、VSZ、RSS、STAT。如果你主要关心进程间的父子关系比如排查僵尸进程是谁生出来的、看某个服务的父进程是不是被换了那ps -ef更直观因为 PPID 正好是第三列一眼就看到。我个人的选择原则是日常巡检看资源占用用ps aux写脚本要处理 PID 和 PPID、看进程启动时间和完整命令用ps -ef。两者没有高低之分用的场景不同而已。2.4 两个都能用但参数别瞎拼ps aux和ps -ef都能展示全部进程区别只是风格和字段。你不需要纠结“哪个才是官方推荐”能解决手头问题就是好的。但我见过有人这么敲ps -aux注意多了一个短横线。在部分系统上这也能用但行为可能有差异-aux在 strict 模式下会被解析成-a -u -x如果-u后面没有参数行为就不完全等价于ps aux了。Linux 的 GNU ps 为了兼容一般会把ps -aux当作ps aux处理但在别的 Unix 系统上不保证。所以老实一点日常用ps aux走-ef风格就写完整的ps -ef不要杂糅。另外这两个风格的参数还可以混搭出新花样比如ps auxf就是ps aux加上树状显示ps -ef --forest也类似。树状结构看进程的父子层级特别直观后面实战会讲到。3. 输出字段逐列拆解看懂这 11 列命令就学会了一半3.1 USER、PID、%CPU、%MEM 各是什么USER进程归属用户。通过它你可以知道进程是 root 跑的还是某个服务账号跑的。排查资源占用时如果某个普通用户的进程 CPU 异常往往需要留意。统计每个用户的进程数量可以这么写ps aux | awk {print $1} | sort | uniq -c前几列一数就出来。PID进程号进程的唯一标识。后面跟kill配合使用比如kill -9 12345。脚本里取 PID 常用pgrep或者ps -eo pid,cmd | grep 关键字。%CPU这里必须讲清楚ps显示的是进程从启动到当前时刻的平均 CPU 占用率不是瞬时值。这个坑我踩过好多次一个进程明明刚在top里飙到 300%回头看ps的 %CPU 却只有 5%那是因为它刚启动不久平均值被拉平了。想看瞬时 CPU用top或pidstat或者自己隔几秒多跑几次ps做差。%MEM进程常驻内存RSS占系统总物理内存的百分比。排查内存问题这列比 VSZ 更有意义因为 VSZ 是虚拟内存不代表实际占用的物理内存。3.2 VSZ 和 RSS预约座位和实际入座的区别VSZVirtual Memory Size虚拟内存大小。简单说进程“能看到的地址空间”有多大包括它申请了但可能永远用不上的内存以及映射进来的文件、共享库等。这个数通常很大看着吓人但略大于实际需求也正常。RSSResident Set Size常驻内存大小。进程实际占用的物理内存页大小。排查内存泄漏时重点盯 RSS如果 RSS 一直涨而 VSZ 涨得不多说明进程在频繁申请物理内存却不释放。拿吃饭举例子VSZ 是你预约的座位数RSS 是实实在在坐下来的客人数量。共享库被很多进程一起用但psRSS是各自统计一遍的所以你如果把所有进程的 RSS 加起来数字会超过物理内存总量这是正常现象别误以为内存泄漏了。真要精确统计一个进程到底吃多少得考虑共享页去重那就比较复杂了日常排查看 RSS 和 %MEM 的趋势就够用。3.3 TTY、START、TIME、COMMAND 怎么读TTY进程关联的控制终端。如果显示?说明这个进程没有终端一般是守护进程、系统服务、内核线程。看一个服务是不是以 daemon 方式跑TTY 为?是特征之一。START启动时间。如果系统刚开机所有进程的 START 都差不多分布在开机时间附近。排查时如果发现某个进程的 START 异常新或者比系统启动时间还早那就值得顺藤摸瓜。TIME累计 CPU 时间注意它和 START 不一样不等于进程跑了多久。TIME 表示进程从出生到现在总共消耗了多少 CPU 时间。一个进程挂了三天但 TIME 才 0:01说明它基本不占 CPU一个进程跑了一小时 TIME 就显示 1:30说明它把 CPU 吃得死死的。COMMAND启动命令。这里重点看路径和参数。排查可疑进程时第一眼就要看 COMMAND 是否正常比如是不是在/tmp下有个奇怪名字的二进制参数里是不是带了一堆随机字符串。想完整显示命令后面要加ww参数否则终端宽度不够会自动截断。4. STAT 状态码一眼看出进程健康度4.1 主状态码R、S、D、T、Z、ISTAT这一列很多人会忽略但它恰恰最能反映进程当前的死活状态。先看主状态码状态含义说明RRunning / Runnable正在运行或在运行队列里排队等待 CPUSSleeping可中断睡眠在等待某个事件IO、信号、锁DUninterruptible Sleep不可中断睡眠通常在内核态等待 IO 完成TStopped被停止通常是被 CtrlZ 挂起或收到 SIGSTOPZZombie僵尸进程子进程退出但父进程没回收IIdle空闲内核线程专用状态R 不一定是坏事CPU 密集型的任务在运行本来就是 R但如果系统负载很高大量进程卡在 R 队列里CPU 就忙不过来了。S 是最常见的状态正常进程大部分时间都在 S等 IO、等定时器、等网络数据这很健康。D 状态要特别注意进程在等不可中断的 IO比如磁盘、NFS、FUSE 等这时候你用kill -9都杀不掉因为它在内核态等着还没回到用户态去处理信号。如果大量进程卡在 D多半是存储或者网络文件系统出了问题。Z 就是僵尸后面专门讲。I 是较新内核给内核线程用的空闲状态不用惊慌。4.2 附加标志位s、l、、N、 怎么组合除了主状态后面还可能跟着小字母ssession leader会话首进程通常是登录 shell 或者带控制终端的进程l多线程进程高优先级nice 值为负N低优先级nice 值为正前台进程组的进程能接收终端输入。组合起来看比如Ss表示这是一个会话首进程正在睡眠状态R表示前台运行的活进程Dl表示一个多线程进程正在做不可中断的 IOT是 CtrlZ 挂起的任务。快速列出僵尸进程可以这样ps aux | awk $8 ~ /^Z/快速列出 D 状态进程ps aux | awk $8 ~ /^D/这些命令在系统异常时特别有用建议直接记下来。5. 排序、过滤、自定义输出把 ps 变成你的专属工具5.1 排序--sort 的用法和坑ps能排序输出这是排查高负载时的高频操作。按 CPU 倒序排出前 20 个进程ps aux --sort-%cpu | head -20按内存倒序排ps aux --sort-%mem | head -20注意--sort前面加-是降序不加就是升序。键名用%cpu、%mem、rss这些都可以。不过有个坑--sort是 GNU ps 的扩展参数Linux 上没问题macOS 的 BSD ps 不认识它。在 macOS 上要排序一般用管道ps aux | sort -k3 -rn | head -20-k3指第三列 %CPU-rn是按数值降序。日常写脚本这类命令要多平台兼容的话建议先uname判断一下系统再决定用哪种排序方式。5.2 过滤grep、awk、pgrep 怎么选最常见的过滤是用管道加grepps aux | grep nginx但这个写法有个著名的坑会匹配到grep自己因为命令行里包含了 nginx 这个字符串输出里会多一行grep nginx。解决办法是加一个方括号技巧ps aux | grep [n]ginx这样grep [n]ginx的命令行不包含完整的 nginx 字符串就不会自匹配。或者用grep -v grep过滤掉那一行。如果只是要取 PID更推荐用pgreppgrep -f nginx pgrep -a nginxpgrep只输出 PID-a同时显示命令行干净利落。脚本里取 PID 用pgrep比grep再awk可靠得多。如果要对某一列做精确匹配就用awkps aux | awk $11 ~ /nginx/ $1 ! root这个意思是命令列包含 nginx 且用户不是 root 的进程都显示出来。实际处理时按需调整。5.3 自定义输出-o 是进阶利器如果嫌ps aux的字段太多太杂或者想在脚本里只取个别字段-o参数很实用ps -eo pid,ppid,user,%cpu,%mem,stat,cmd --sort-%cpu | head-e表示所有进程后面的pid,ppid,user,...是你想输出的列顺序完全自定义。这样写脚本的时候输出格式稳定awk处理起来就不用数第几列了。常见的可用字段有pid、ppid进程号和父进程号user、group用户和用户组%cpu、%memCPU、内存占用vsz、rss虚拟内存、常驻内存stat状态码start、etime、time启动时间、运行时常、累计 CPU 时间cmd、args完整命令行nlwp线程数wchan进程在内核中等待的内核函数。去掉表头可以用结尾比如ps -C nginx -o pid-C表示按进程名匹配-o pid表示只输出 PID 且不带表头。这在脚本里判断 nginx 有没有在跑非常方便输出有值说明在跑没有说明挂了。实用性极高。6. 实战场景这几招排查问题够用了6.1 模拟 top 的高 CPU、高内存 TOP10机器负载高的时候第一步就是找出谁在吃 CPU、谁在吃内存# 高 CPU TOP20 ps aux --sort-%cpu | head -21 # 高内存 TOP20 ps aux --sort-%mem | head -21我自己会在 shell 里绑定两个别名alias pscpups aux --sort-%cpu | head -20 alias psmemps aux --sort-%mem | head -20不过再强调一遍ps里的%CPU是平均占比不是瞬时值。如果ps排出来 CPU 不高但机器又明显卡顿请用top -b -n 1 -o %CPU | head -20或者pidstat 1 5看实时数据。排查问题时先看瞬时再回看平均才是比较完整的思路。6.2 僵尸进程排查与处理僵尸进程Z是运维面试题里的常客。先在系统里找出来ps aux | awk $8 ~ /^Z/输出里能看到僵尸进程的 PID 和 COMMAND。关键要找到它的父进程僵尸进程杀不掉因为已经死了真正的问题在父进程没调用wait()回收。看父进程ps -o pid,ppid,state,cmd -p 12345或者直接列出所有 PID 旁边的 PPIDps -eo pid,ppid,stat,cmd | awk $3 Z处理方式通常有两种。如果父进程还在跑kill -9干掉父进程僵尸进程会被 initPID 1收养并清理。如果父进程是 PID 1那一般系统会自动清理如果 PID 1 下面还挂着僵尸那可能是内核或特别深层的 bug最常见的是父进程已经死了僵尸被 systemd 收养但某些子系统没有正确回收这种情况往往需要重启机器来彻底清掉。少量僵尸进程不占 CPU 不占内存只是占个 PID 表项不用过度恐慌但数量持续增长就要警惕父进程是不是有资源泄漏问题。6.3 找进程的线程情况Java 应用、Go 服务、nginx 多线程 worker都需要看线程维度。列出所有进程的线程ps -eLf | head这里每一行代表一个线程LWP列是线程 IDNLWP列是该进程总的线程数。看单个进程的线程ps -T -p 12345排查线程数量是否异常暴涨、或者某个进程是不是多线程吃满 CPU这几条命令足够用了。再配合top -H -p 12345就能看到每个线程的实时 CPU定位到具体线程号后Java 应用还能用jstack把线程堆栈打出来。6.4 查看异常启动的进程和完整命令有时候 COMMAND 列太长被截断你只看到一半路径这时候加ww参数让输出不截断ps auxww | grep suspicious如果想看哪些进程是刚启动的可以按启动时间排序ps -eo pid,ppid,user,etime,cmd --sortetime | tail -20etime是按运行时长排序正序的话最后的进程就是启动时间最新的。反过来想看跑得最久的进程ps -eo pid,ppid,user,etime,cmd --sort-etime | head -20排查异常进程时这三个命令我基本必敲先看 COMMAND 有没有奇怪的路径再看启动时间是不是恰好对应某个可疑事件点最后看 PPID 确认它的父进程是谁。这套组合思路比单独执行某一条命令有用得多。7. 常见问题和踩坑记录7.1 %CPU 超过 100% 正常吗正常。ps的%CPU是相对单核的计算方式一个进程有多个线程跑在多个核上CPU 总占比可以超过 100%。如果你看到 400%说明它基本上吃满了四个核心。这是符合预期的不用惊讶。但如果是在介绍 CPU 占用时想给别人传达“占满了几个核”记得把 100% 当作一个核的口径否则容易误读。脚本里如果拿%CPU做阈值判断也要清楚它是平均值不是瞬时值短时间尖峰可能被平均掉。要想准确用top或pidstat采样。7.2 容器里执行 ps 看不到宿主进程容器用的是 PID namespace 隔离容器里执行ps默认只看到容器内的进程看不到宿主机的。所以经常有人在容器里排查问题发现“进程没了但服务还活着”其实进程在宿主上跑着只是你看不到。解决办法有几种一是以特权模式运行容器并共享 PID namespace--pidhost二是在容器里把宿主机的/proc挂载进来再执行ps三是直接在宿主机上用docker top 容器名查看容器的进程。如果容器里连ps都没有可以用cat /proc/1/comm凑合看一下 1 号进程是不是你要找的这招在精简容器里经常能用上。7.3 ps 命令不存在或被精简有些精简的基础镜像、容器环境里没有ps因为没有装 procps 或 procps-ng。临时解决# Debian/Ubuntu apt-get update apt-get install -y procps # CentOS/RHEL yum install -y procps-ng如果连安装包都不想装也可以直接遍历/proc目录for p in /proc/[0-9]*; do echo ${p#/proc/}: $(cat $p/comm); done这个思路派生于最开始的原理理解了/proc你就不会再害怕任何精简环境。7.4 命令太长被截断ps输出到终端时会根据终端宽度截断 COMMAND 列导致你只能看到命令的一部分。解决方法是加wwps auxww | grep java或者-eo模式下指定args字段并加宽ps -eo pid,args --sort-%cpu | head我写脚本抓进程命令行时基本默认就用-eo pid,args这样格式确定、不会截断也不容易因为终端宽度变化导致解析出错。7.5 TIME 和 STIME/START 的区别这也是一个高频混淆点。STIME和START表示进程的启动时间点而TIME表示进程累计消耗的 CPU 时间。要看“进程跑了多久”用etime字段ps -eo pid,etime,cmd | grep 进程名输出的3-02:15:30表示这个进程已经运行了 3 天 2 小时 15 分 30 秒。想精确到秒的启动时间点用lstart字段ps -eo pid,lstart,cmd | grep 进程名这两个字段在排查“进程是什么时候起来的”“到底跑了多久”时比START更精确。我在实际操作中还有个习惯把ps aux --sort-%cpu和ps aux --sort-%mem这两条命令直接存成 shell 函数在.bashrc里放好遇到机器异常第一时间就能排出嫌疑进程。另外面试时如果被问到 Linux 排查思路从这个入口讲起再带上ps -ef、/proc原理和状态码的解读基本就能让面试官相信你不是只会敲命令的搬运工。说到底ps不难难的是理解它背后的进程模型和/proc数据源理解了这些参数都是浮云。