查进程关系这件事,我踩过不少坑。刚接触 Linux 那阵子,机器一卡就先ps aux拉一屏输出,然后瞪着 PPID 那一列硬找父子关系。进程少的机器还好说,碰到开了几十个 worker 的 Web 服务、挂了一堆子任务的 Java 应用,眼睛是真花。后来有人甩给我一句“你试试 pstree”,我跑了一下当场就服了:进程之间的亲缘关系像一棵树一样铺开,谁拉起谁、谁挂在谁下面,一眼扫过去就明白。这篇就把 CentOS 7.6 下pstree的用法和适用场景完整拆一遍,适合刚开始学 Linux 命令的运维新人,也适合那些和我一样长期靠ps干活、想换个更直观视角的老朋友。
1. 为什么进程关系要“看出树形”,而不是只靠 ps
1.1 ps 的扁平输出和人的直觉不对齐
先解释一个最基本的困惑:进程信息明明都在ps里,为什么还要单独学一个命令?
因为ps的本质是“进程当前状态的快照”,它的输出是扁平的。哪怕你用ps -ef看到了 PPID(父进程 PID),也得在同一屏输出里搜索“哪一行的 PID 等于这个 PPID”,多找几层就乱了。尤其线上机器动不动几百个线程,ps aux一滚到底,想回答“这个 worker 是谁拉起来的”“这组子进程为什么没有人管”,效率极低。
而pstree做的就是一件事:把进程表重新组织成一棵从根到叶的关系树。树的根一般是 PID 1(CentOS 7.6 上是 systemd),每个进程按父子关系挂在对应节点下面,折叠、展开都有一套自己的规则。它不丢进程信息,只是换了种组织方式,让“关系”这种本来是隐式的结构直接显性化。
我记得第一次用pstree的场景:一台机器上跑着三个 Java 应用,top里看到其中一个 java 进程 CPU 100%,但不确定它到底归谁管。ps -ef | grep java出来七八行,照着手工对 PPID 对到第三层就对岔了。用pstree -p一眼看到某个 java 进程挂在另一个 java 主进程下面,才知道原来这机器上有个应用做了父子进程嵌套的部署结构。从那以后,排查进程问题我基本是先pstree后ps。
1.2 树形结构能直接回答的三个问题
进程树不是好看,而是能直接回答平时排查里最常问的三个问题:
第一,这个进程的“老板”是谁。一个进程突然死了或者 CPU 飙高,你得知道是谁拉起来的,才能判断是该杀进程还是该杀父进程重启服务。pstree -s <PID>能把从 PID 1 到目标进程的完整链条拉出来,不用你自己一层层找。
第二,一组子进程是不是“散养”状态。进程如果没有稳定的父进程管理,它会被系统自动挂到 PID 1 名下。这种“孤儿进程”既不好重启,也容易出现资源回收不及时的问题。pstree里看到一堆进程直接挂在 systemd 节点下面,就说明它们已经不是原来的归属关系了,是排查“服务停止后进程还在”这类问题的重要线索。
第三,同类型进程有多少个、是主进程还是 worker。nginx、php-fpm这类服务都是“主进程 + 多个同名子进程”的结构,pstree会自动把相同名字的子进程折叠成8*[nginx]这种简写,数量关系一目了然。这比grep出来一行行数要直观得多。
1.3 在 CentOS 7.6 上先装好 psmisc
CentOS 7.6 的最小化安装不一定带pstree,因为pstree属于psmisc软件包。我第一次在这台系统上直接敲pstree就吃了“command not found”的亏。
安装很简单:
yum install -y psmiscpsmisc包里除了pstree,还带了killall和fuser,都是运维排查的高频命令,装了不亏。装完验证一下版本:
pstree -V能正常输出版本号就行。老版本和较新版本的输出样式会有细节差异,比如树形符号、是否默认显示线程,这些后面在参数部分会具体说。以 CentOS 7.6 自带的 psmisc 版本为准,用之前可以先pstree --help扫一眼支持哪些选项,同一个命令在不同发行版之间的参数兼容性并没有想象中那么一致。
2. 读懂 pstree 的输出:树的读法、折叠规则和那些括号
2.1 从根到叶:pstree 默认画的是全系统的进程家族
不指定任何参数直接跑pstree,输出的是从 PID 1 开始的整个用户态进程树。第一行通常是:
systemd─┬─NetworkManager───2*[dhclient] ├─auditd───{auditd} ├─crond ├─dbus-daemon───{dbus-daemon} ├─sshd───sshd───bash───pstree这里面有几个需要习惯的符号:
systemd是第一层的根节点,它是当前系统的“初始进程”,所有用户态进程都直接或间接挂在它下面。- 垂直分支用
│或├─、└─来标示,─┬─表示当前节点下面还有多个子分支。 sshd───sshd───bash───pstree是一条直线,说明这几个进程是连续的单链关系:SSH 服务、你登录后的 shell、以及你正在跑的pstree自己。如果这条链太长,说明中间有连续的子进程嵌套,比如通过几层 SSH 跳板又是另一回事,但单机上这种链一般不会太长。
新手最容易忽略的一点是:pstree默认会折叠相同的子进程,折叠后用一个中括号加数量表示。比如:
NetworkManager───2*[dhclient]这不是说 NetworkManager 下面只有一个叫 dhclient 的进程,而是有两个名字完全一样的 dhclient 子进程,被压缩成了一条。这个设计是有道理的——不然几十个相同的 worker 会把整屏刷爆。想看到被折叠的每一项,加-c参数让 pstree 不做合并,那些重复进程会一个个列出来。
2.2 大括号里的是线程,中括号里是折叠的进程
很多人第一次看输出会混淆这两类括号,我单独拎出来说。
{}(大括号)表示的是线程。进程里线程数量多的时候,pstree 会把同一进程的线程显示成:
java───29*[{java}]意思是这个 java 进程内部有 29 个 java 线程被折叠了。用-T参数可以隐藏线程,只看进程级的关系;如果你需要确认某个线程是不是存在,不加-T反而更方便。
[](中括号)表示的是被折叠的重复子进程。就像前面2*[dhclient]那样,2*是数量,[dhclient]是进程名。两者出现的位置不一样:线程是挂在某个进程后面的“内部成员”,中括号折叠的是真正的子进程。
关于内核线程,CentOS 7.6 上你还会看到一类行:
systemd─┬─kthreadd───2*[kworker]kthreadd底下那些[kworker]是内核线程,本质上也是一种线程展示,只是它们属于内核态,平时排查应用问题时一般不用管,但知道它们为什么在树里,能避免在分析时被一堆kworker吓到。
2.3 状态不明的时候,别光靠 pstree,要和 ps 配合
有朋友问我:pstree 里能不能看到僵尸进程?这里要澄清一下:很多人以为树里会标 Z,实际上 pstree 的输出里不会直接标注进程状态。它显示的是进程层级、PID、命令行参数、线程关系,不负责显示 R/S/D/Z 这种状态码。
那怎么用 pstree 发现僵尸?我的习惯是先用pstree -p找到可疑的进程节点和它的父进程,再针对这个 PID 去ps -o pid,ppid,stat,cmd -p <PID>确认状态。举个例子,如果你发现某个进程下面挂着一个名字很怪、而且已经很久不变化的子进程,用ps查它的 STAT 是Z,基本就能确定是僵尸进程。pstree 的作用是把“它属于谁”这个问题讲清楚,真正的状态判定交给ps去完成,工具各管一段。
这个过程我在后面章 4 的实际案例里会展开,先记住一个原则:pstree 是你的望远镜,帮你把焦点对准目标;ps 是你的放大镜,帮你确认目标的细节。
3. 核心参数详解:从最常用的 -p 到组合排查套路
3.1 -p:让树上的每个节点带上 PID
-p是使用频率最高的参数,没有之一。进程名可能有重名,PID 才能精确定位。加-p后输出变成:
systemd(1)─┬─NetworkManager(950)───2*[dhclient(3871)] ├─sshd(1278)───sshd(2689)───bash(2690)───pstree(2712)看到这种输出,你要查某个进程,直接拿括号里的 PID 去top、lsof、ps里接着查就行。排查的第一步我永远先pstree -p,相当于给整个系统的进程结构先画一张带门牌号的地图。
一个常见的坑:用了-p之后,进程名后面那个括号里的 PID 很容易和[dhclient(3871)]这种折叠混合在一起,看起来特别乱。这时候先别慌,中间加个-c把折叠展开,或者直接在grep时带上-E过滤你要的进程名,比如:
pstree -pc | grep -E "nginx|java|ssh"这样可以在大树里快速找到目标进程分支。
3.2 -a:把命令行参数也挂到树上
pstree -a会把进程的命令行参数追加显示在进程名后面。比如:
sshd───sshd───bash───pstree -a多出来的是你正在执行的命令行参数。这个参数在排查多实例部署时特别有用。举个例子,一台机器上同一个二进制的服务跑了多个实例,光看进程名分不清哪个是哪个,-a会直接显示各自的配置文件路径、启动参数,省去你逐个去/proc/<PID>/cmdline里翻。
不过要注意,-a的输出会变得更长,尤其是挂了很多参数的进程,行会被截断。这种情况加-l(见 3.6)能保留完整行,后面再讲。
3.3 -s:直接画出到目标进程的完整祖先链
这是我后来才学会、但越来越依赖的参数。pstree -s <PID>不看整棵全树,只输出从 PID 1 到指定进程的完整链路。比如:
pstree -s 2690输出类似:
systemd(1)───sshd(1278)───sshd(2689)───bash(2690)这比整棵树的输出清爽太多。定位“某个进程到底是谁拉起来的”这种问题,-s是最快路径,不用手工对 PPID,系统直接给你算好。
我一般排查“服务明明停了但端口还占着”的问题时,会先lsof -i :端口拿到 PID,然后pstree -s <PID>,几秒就能确认这个进程是残留的孤儿(链路终点挂在某个老父进程下),还是 systemd 管理的服务自动重启了。省下的时间不是一点半点。
3.4 -u:显示用户切换点
-u会在进程树里标注 UID 发生切换的位置。Linux 服务启动时经常有“以 root 启动、降权运行子进程”的套路,比如某些 Web 服务的主进程是 root,worker 子进程降权成普通用户。默认输出里你看不出谁在哪个 UID 下,加上-u后,用户切换的节点会用括号标出来:
httpd(0)─┬─httpd(48)─┬─httpd(48) │ ├─httpd(48)这里的数字变化告诉你主进程以 root(UID 0)运行,子进程切换到了别的用户。这个参数在排查“为什么服务没权限写文件”时比较直观:看一眼树,发现原来父进程是 root,子进程用户不对,权限问题基本就有方向了。
3.5 -n:按 PID 排序,而不是按进程名
默认pstree是按进程名字典序排列同级子进程的,有时候你想按启动顺序捋一捋,名字排序帮不上忙。加-n后,同一节点的子进程按 PID 从小到大排列。启动时间早的 PID 小,所以-n能反映出基本的创建先后顺序。这个参数我在观察某个父进程下面子进程的“出生顺序”时用过,比如确认某个 worker 是不是最晚启动的那个、能不能通过 PID 差判断重启范围。
不过说实话,-n日常用得不算多,记住“有这样一个参数能改变排序”就够了,真需要的时候翻 help 也来得及。
3.6 剩下几个参数和速查表
除了上面几个,还有几个参数按需使用:
-c:不折叠相同子进程。想看到全部重复的 worker 实例时用。-T:隐藏线程,只看进程级结构,输出会干净很多。-l:不截断长行。输出到终端时,pstree默认会根据终端宽度截断过长的分支,-l会把完整内容打出来。-h:高亮当前进程及其所有祖先节点,类似-H但指定高亮当前用户进程。实际配合终端颜色查看时,能快速在树里找到自己 shell 的位置。-V:查看版本信息。
下面是我整理的一个速查表,方便抄作业:
| 参数 | 作用 | 推荐场景 |
|---|---|---|
-p | 显示 PID | 排查时给进程树加门牌号 |
-a | 显示命令行参数 | 区分同名进程的不同实例 |
-s <PID> | 显示目标进程的完整祖先链 | 快速定位进程归属 |
-c | 不折叠重复子进程 | 想看全部 worker / 子进程明细 |
-T | 隐藏线程 | 只想看进程级关系 |
-u | 显示用户切换点 | 排查权限问题 |
-n | 按 PID 排序 | 观察子进程启动顺序 |
-l | 不截断长行 | 输出过长被截断时使用 |
组合的话,我最常用的组合是pstree -pc <进程名>和pstree -sp <PID>,一个看全貌,一个看单点链路,基本覆盖日常 80% 的排查需求。
4. 一次实战:用 pstree 定位“孤儿进程”和 CPU 飙高的源头
理论说完了,把我在一台 CentOS 7.6 机器上实际排查过的过程还原一遍,你会更清楚这些参数在真实场景里怎么串起来用。
4.1 场景一:进程变成“孤儿”,找不到原来的父进程
那是一个内部工具服务,平时由某个管理进程统一拉起和回收子任务。某天我做发布,把管理进程停掉了,结果发现它之前拉起来的几个子任务进程还活着,而且还在正常发数据。这就有问题了:管理进程都已经停了,这些子任务按理说应该跟着退出,怎么还赖着不走?
先用ps aux看了一下这些进程的 PPID,发现变成 1 了。这就是典型的“孤儿进程”:父进程退出后,系统把子进程的父进程重设为 PID 1,也就是 systemd 来“收养”。原因可能是子进程没有正确处理父进程退出信号,或者父进程退出时用了极端方式,没来得及通知子进程。
这时候pstree -s直接把这几个残留进程的祖先链画出来:
pstree -sp <残留PID>输出类似:
systemd(1)───工具子任务(30241)看到 systemd 在最顶上,就知道这些进程已经不在原来的管理链里了,变成了“挂靠孤儿”。如果是ps来查,你得先找到一堆 PPID=1 的进程,再一个一个确认;而pstree -s直接给你答案,区别就像翻通讯录和直接搜名字。
处理也简单:既然确认是孤儿且业务上不需要了,直接 kill 掉再重启正常流程,然后用pstree确认这两个 PID 已经从树里消失,新任务全部挂在正确的管理进程下面。
4.2 场景二:Java 服务 CPU 100%,先看它在树里的位置
另一个更常见的场景:线上某台机器 CPU 飙高,top排序后看到某个进程占了接近 100% 的 CPU。这时候我的标准动作路线是这样的:
第一步,top里记下 PID(比如 20140)。
第二步,直接pstree -p | grep 20140,看到它挂在哪个父进程底下。
第三步,pstree -s 20140,拿到完整祖先链。
我当时查下来发现,这个 java 进程并不像我想象的那样是 systemd 直接管理的服务进程,而是挂在一个调度器进程下面,调度器又是被另一个“任务分发服务”拉起来的。也就是说,这个 CPU 飙高不是常态服务在跑,而是某个任务调度链路的环节出了问题。
按这个线索去查任务日志,定位到是调度器反复给这个子进程派同一个任务,导致它在死循环处理。如果一开始只盯着ps aux里的 CPU 高进程杀,杀完可能又被父进程重新拉起来,治标不治本。pstree的价值就在这里:它帮你把“因果链条”画出来,让你知道应该去父进程那一层做控制,而不是在子进程上反复救火。
4.3 实战心得:pstree 的“无输出”也是重要线索
排查中还有一个容易被忽略的点:pstree -p | grep 某个PID如果没输出,那很可能这个进程已经不存在或者变成了著名的不可能 grep 到自己的问题。
这个在小技巧里我用过不止一次。你明明在ps里看到一个进程,结果pstree里找不到?大概率是它已经处于将死状态、正在退出,只是进程表还没清干净。反过来,如果你删除了某个进程,但pstree里还能看到它挂在 systemd 下面,而且一直没有消失,那就要怀疑是僵尸进程了。
5. CentOS 7.6 使用 pstree 的注意事项和易踩的坑
5.1 最小化安装没有 pstree,别管它叫“命令内置”
前面提过,CentOS 7.6 最小化安装不带 pstree。很多新手在网上看到命令就直接敲,报错了以为系统有问题,其实只是包没装。这个问题再强调一遍:pstree是 psmisc 包里的工具,不是 shell 内置命令,也不是 coreutils 自带的。如果你在别的 Linux 发型版上,安装方式可能不同,但在 CentOS 7.6 就是:
yum install -y psmisc装完之后,a 命令还提供了killall和fuser,其中fuser -v 端口/tcp查端口占用比lsof在某些精简环境下更稳,顺手受益。
5.2 输出太长被截断,加 -l
我在 3.6 里提到过-l,但这里必须单独拿出来讲,因为这是实际使用中最常出现的“假故障”。默认pstree输出到一个宽度有限的终端时,遇到某条链特别长,它会在某个进程节点上直接截断,导致你看到:
systemd─┬─阿巴阿巴───…那个…的意思只是“后面还有内容,但这一行放不下了”,并不是进程树真的断了。加-l后它会完整输出长行。如果你用脚本抓取 pstree 的输出做自动化分析,强烈建议加上-l,否则你拿到的可能是被截断的残缺数据,解析时容易出 bug。
我习惯把pstree -p的输出保存到日志里排查历史问题,命令是:
pstree -pl > /tmp/process_tree_$(date +%Y%m%d_%H%M%S).log文件里不用考虑终端宽度,但保险起见还是会加-l,就是防着某些时候输出宽度的计算方式和终端不一致。
5.3 容器里运行 pstree,看到的是自己世界的树
很多环境里已经有 Docker 这类容器技术了,pstree 在容器里会出现一个很有意思的现象:你在容器内看到 pstree 的根不是宿主机上的 PID 1,而是容器内的入口进程。这是 Linux PID namespace 在起作用,pstree 遵守当前命名空间的边界。
排查时如果发现容器内 pstree 的树“少了什么”,先别怀疑命令有问题,想想你是不是切进了 namespace 隔离的环境。想看宿主机全局进程,要在宿主机上执行,或者在容器内确认自己是否有权限访问宿主机的 /proc。这个在 CentOS 7.6 上有时候会让人困惑,但它不是 pstree 的 bug,而是隔离机制的正常表现。
5.4 配合 watch 实现“实时进程树”
静态看一次树不够,排查一些“进程一会儿在、一会儿不在”的问题时,可以配合watch来定时刷新:
watch -n 2 'pstree -pc | grep -E "java|nginx"'watch每两秒跑一次这个命令,输出自动刷新,屏幕高亮区保持干净。你可以在几秒内看到目标进程是持续存在还是时有时无。这个组合是我在观察某些不停重启的服务时最常用的一招,比反复手动敲命令高效得多。
有一点要注意:watch默认会把你的命令放在一个 shell 里执行,如果命令里有管道符或引号,要像上面那样用单引号把完整命令包起来,否则管道会在你的交互 shell 里先被解释,效果就不对了。我第一次用的时候就没包引号,结果 watch 一直在刷新一个固定的错误提示,白白折腾了几分钟。
5.5 一个我长期在用的快捷函数
最后分享一个我放在/etc/profile.d/下面的小函数,就是给 pstree 包了一层常用参数:
pt() { if [ -z "$1" ]; then pstree -pc else pstree -pc | grep -E "$1|PID" fi }用法很简单:敲pt看全树,敲pt java只看包含 java 的进程分支。以前我每次都要敲pstree -pc | grep java,现在一个字符选择少打很多,排查也更顺手。
如果你需要精确定位某个 PID,再单独配合pstree -sp <PID>。
pstree 这东西,功能不复杂,复杂的是在什么场景下想起用它。我现在排查进程相关问题的顺序基本固定下来了:先pstree -sp看归属,再ps -o stat看状态,最后才决定杀还是不杀、改哪里。心里有棵树,比手里有一堆 PID 踏实很多。