免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Linux PID排查全攻略:ps命令与进程信号管理

Linux PID排查全攻略:ps命令与进程信号管理 1. 排查进程先弄清 PIDps 命令的价值与使用前提在 Linux 上工作不管是写脚本、维护服务器还是排查线上问题几乎都绕不开一件事找到某个进程的 PID。我们常说的 PID即 Process ID也就是操作系统给每个运行中的进程分配的唯一编号。这一串数字是进程的“身份证号”你后续要关掉进程、查看进程资源占用、给进程发信号、调整优先级全都得靠它。我第一次接触 ps 命令时其实很困惑系统里明明有 top、有 htop、甚至打开系统监视器也能看到进程列表为什么还要用一个看起来输出乱糟糟的命令行工具后来真正开始排查问题时才明白ps 是“查询快照”最轻量、最直接的手段它不依赖图形界面也不要求你额外安装软件在任何 Linux 发行版上默认都有。ps这个名字就是 Process Status 的缩写它的作用就是把当前系统里的进程状态按照你指定的格式打印出来而在默认输出里PID 就是最重要的字段之一。另外要特别说明在自动化控制和嵌入式领域“PID”通常指比例-积分-微分控制器Proportional-Integral-Derivative但本文讨论的“PID”和那个 PID 算法没有任何关系。如果你是在搜“pid控制”、“pid算法”时误入这里的可以先收藏文章看完 Linux 进程查看再回去调你的控制器两边虽然都叫 PID但完全是两个世界。这篇内容适合所有 Linux 使用者刚接触命令行的新手可以跟着把基础参数吃透有一定经验的同学可以重点看后半部分的实战组合和踩坑记录正在准备运维或后端开发面试的人也能在最后看到经典考点和标准回答思路。我会从“为什么必须会看 PID”讲起逐步拆解 ps 命令的参数、字段含义再演示各种组合用法最后聊几个工作中经常踩的坑。2. ps 的常用参数与输出字段先学会读表再动手2.1 最常用的组合ps -ef 和 ps aux 到底差在哪几乎在所有教程里你都会看到两个命令被反复提及ps -ef和ps aux。它们都能显示全部进程但来源不同输出格式也有差异很多新手花了好久才搞明白这两者的区别。ps -ef是 System V 风格的写法。-e表示显示所有进程-f表示完整格式full format输出里会包含 UID、PID、PPID、C、STIME、TTY、TIME、CMD 这八列。你可以把-ef理解成“把系统里所有进程的完整信息列出来”它逻辑清晰列字段少而固定非常适合脚本处理。而ps aux是 BSD 风格的写法。a表示显示所有终端上的进程包括其他用户的u表示以用户为主的格式来显示x则表示显示没有控制终端的进程。注意前面的-号不是随便省略的在 Linux 的 ps 里带横线的参数和不带横线的参数混合使用时语义会发生变化。ps aux会输出 USER、PID、%CPU、%MEM、VSZ、RSS、TTY、STAT、START、TIME、COMMAND 这些列比ps -ef多了 CPU 占用率、内存占用率、虚拟内存大小、物理内存大小以及进程状态。实际使用中快速查看“谁在跑、占了多少资源”我通常用ps aux处理脚本逻辑、提取 PID 列我更喜欢ps -ef因为它的字段固定且顺序稳定awk {print $2}就能拿到 PID 列。两者并不冲突建议都记住大部分运维场景下混用也没问题。下面用一个表格直观对比这两种常见写法的差异命令写法风格典型输出列适用场景ps -efSystem V带横线UID PID PPID C STIME TTY TIME CMD查看进程关系、脚本提取 PIDps auxBSD不带横线USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND查看资源占用、分析进程状态2.2 输出字段逐列拆解PID、PPID、STAT、TIME 分别是什么意思很多人记不住 ps 输出里的字段其实是因为没有意识到这些字段背后对应着进程的完整生命周期。我一个个拆开讲。UID或USER是启动这个进程的用户。为什么有时候一个进程杀不掉很可能因为它属于 root而你当前是普通用户。PID自然是进程唯一编号PPID是它的父进程编号也就是“谁生出了这个进程”。比如你在终端里敲了一条命令这条命令产生的进程 PPID 通常就是当前 bash 的 PID。字段C在ps -ef里表示 CPU 利用率百分比是一个粗略值和ps aux里的%CPU类似。STIME是进程启动时间注意它只精确到日期或时分想看出完整的启动时刻可以用ps -o lstart查看。TTY是进程关联的终端如果你的进程是后台守护进程没有占用终端这列会显示?。这其实是一个非常有用的筛选条件看到?就知道这大概率是一个 daemon 进程。TIME是最容易被人误解的字段它根本不是进程运行了多长时间而是这个进程累计消耗的 CPU 时间。一个进程跑了三天三夜如果期间基本在睡觉TIME可能只有 0 分 0 秒反过来一个刚启动五分钟就疯狂计算的进程TIME可能已经到了 4:30。所以看到TIME数值很大说明这个进程确实“烧了不少 CPU”。CMD或COMMAND是启动进程的命令行。如果命令行太长ps 默认会截断想要看到完整命令可以用ps -efww其中-w表示无限宽度输出。这个参数在排查 Java 服务、Python 脚本参数时经常用到。STAT是进程状态在ps aux里能看到。常见的状态码含义如下状态码含义常见场景R正在运行或可运行当前在 CPU 上执行或排队S可中断睡眠等待某个事件比如等用户输入D不可中断睡眠等待 I/O通常和磁盘操作相关Z僵尸进程子进程已结束但父进程还没回收T已停止收到 SIGSTOP 或 SIGTSTP 后被暂停I空闲内核线程不可中断睡眠的内核线程状态码后面偶尔还会带额外字符比如S表示前台进程组里的进程Ss表示这个进程是会话领导者。实际排查中你重点盯住 Z一旦看到很多 Z说明系统里出现了孤儿或僵尸处理不当的问题这个在后面会专门讲。2.3 ps 的选项风格BSD 风格与 GNU 风格的差异ps 命令在 Linux 上比较特殊它同时支持三种选项风格UNIX 风格带单个-前缀如ps -e、BSD 风格不带-前缀如ps aux、GNU 长选项双-前缀如ps --forest。这也是很多人刚接触 ps 时懵掉的主要原因。除了文章开头解释的-ef和aux我还常混用这两种风格比如ps -ef --forest前面用 UNIX 风格的-ef后面接一个 GNU 长选项--forest这样能用树状缩进展示父子进程关系。听上去有点“混搭”实际上完全合法也很实用。再给你一个脚本利器ps -o可以自定义输出列。比如我只想看 PID、父进程 PID 和命令行可以这样写ps -eo pid,ppid,cmd这里的-e表示所有进程-o后面跟逗号分隔的字段列表。还可以给列加自定义标题比如ps -eo pid进程号,ppid父进程,cmd命令行输出结果就会显示中文或英文表头。这个技巧在做自动化巡检脚本时很管用可以精确截取你需要的信息而不是被一屏无关字段干扰。3. 查特定进程 PID 的实战路径别再对着一整屏输出发呆3.1 用 ps 加管道 grep 过滤进程名的基本姿势直接执行ps -ef会输出几十甚至几百行内容没人愿意用肉眼去翻。最常见的做法就是后面再接一个grep配合管道符号过滤出目标进程。ps -ef | grep nginx这条命令会输出所有命令行里包含 nginx 的进程。你得知道有个经典小坑grep 本身也是一个进程它的命令行里同样含有“nginx”这几个字符所以结果里会出现一条grep nginx的记录。如果你把这个结果交给脚本去统计进程数量一定会多算出一个“假进程”。解决办法有两种一是用grep -v grep把这条记录排除掉二是用字符组技巧写成grep [n]ginx这样 grep 启动时命令行里是[n]ginx正则匹配的是nginx自己反而不匹配自己。第二种写法更优雅也是不少高手在脚本里惯用的手法。如果你想看得更结构化可以在 grep 后面加--colorauto高亮匹配内容或者在ps -ef后面加ww参数防止命令行被截断ps -efww | grep [n]ginx3.2 更精准的替代方案pgrep 与 pidof 的对比如果只是单纯想拿到 PID其实没必要把完整进程列表先打出来再过滤Linux 提供了更直接的命令pgrep和pidof。pgrep按进程名返回 PID 列表默认只匹配进程名称而不是完整命令行。它支持按精确名称匹配比如pgrep -x nginx只匹配进程名叫nginx的pgrep nginx则会匹配所有名称里包含 nginx 的进程。想同时看到 PID 和完整启动命令加-a参数pgrep -a nginxpgrep -f是另一个我经常用的选项它按照完整命令行匹配。比如你跑了一个python app.py --port8080用pgrep python可能匹配到一堆无关的 Python 进程但用pgrep -f app.py --port8080就能精准定位。pidof则更简单直接它根据进程名返回 PID命令格式是pidof nginx。和 pgrep 的差别在于pidof 要求进程名称完全匹配它不会做模糊匹配也不会匹配命令行参数。如果你的 nginx 有多个 workerpidof nginx会把所有 PID 一次性列出来空格分隔配合kill使用非常方便。3.3 拿端口反查进程 PID 的完整链路排查线上问题时更常见的一种需求是某个端口被占用了我怎么知道是哪个进程在用ps 本身不负责查端口但它可以和ss、lsof配合起来完成反查。先看端口监听情况sudo ss -tlnp输出里的-t表示 TCP-l表示监听状态的端口-n不做域名解析-p显示对应进程信息。加上-p之后你会看到users:((nginx,pid12345,fd6))这样的结果pid12345就是占用该端口的进程 PID。拿到 PID 之后再用 ps 查看进程详细信息ps -fp 12345如果你的系统没有 ss也可以使用lsof -i :端口号来查询。比如sudo lsof -i :8080这条命令会列出所有占用 8080 端口的进程输出里包含 COMMAND、PID、USER 等信息。注意普通用户通常看不到别人进程的详细信息所以命令前面要加sudo不然只能看到一片?或者报没有权限。3.4 找父进程和查进程树理解 PPID 的价值有时候你拿到一个 PID还想知道它是谁拉起来的。比如系统里有一个可疑的进程你顺着它的 PPID 往上找往往能找到真正的启动源头。最简单的查看方式是用ps -fp 目标PID输出里的 PPID 列就是父进程编号。再继续对 PPID 执行同样的命令就能一路溯源到 init 或 systemd。更直观的方法是用pstree -ppstree -p 12345这个命令会以树状结构显示 PID 12345 以及它的所有子进程。ps -ef --forest也有类似效果不过那是全局视角进程多的时候反而不容易聚焦。PPID 还有一个非常现实的用途判断某个服务是不是被守护进程托管着。比如你用 systemd 启动了一个服务那么它的 PPID 大概率是 1systemd。如果你在一个终端里手动执行了一个后台任务它的 PPID 可能是当前 bash 的 PID一旦你关掉终端这个任务可能变成孤儿进程被 init 或 systemd 收养PPID 变成 1。4. 拿到 PID 之后的进程管理kill 的信号与父子关系实地操作4.1 kill 命令的本质不是“杀死”而是“发信号”很多初学者把kill当成“杀无赦”总觉得用它就能把进程处决掉。其实kill命令的真实作用是向进程发送一个信号而“终止进程”只是众多信号处理动作里的一个。理解这一点你就知道为什么有些时候kill PID会没效果以及为什么kill -9总被当作最后手段。先看kill -l会列出所有支持的信号名称和编号。其中必须记住的是这些信号名编号默认行为典型用途SIGTERM15终止进程kill 默认发送的信号允许进程清理资源SIGKILL9强制终止不可被捕获或忽略进程不响应 SIGTERM 时使用SIGSTOP19暂停进程相当于暂停不终止SIGCONT18继续运行让暂停的进程恢复SIGHUP1挂断很多守护进程用来重载配置SIGINT2中断等价于按 CtrlCkill 12345等价于kill -15 12345多数服务收到 SIGTERM 后会有代码专门处理比如保存数据、释放端口、通知子进程退出然后正常退出。这其实是最优雅的关闭方式。如果进程对 SIGTERM 置之不理你再用kill -9 PID强行结束也不迟。直接一上来就kill -9很有可能导致数据损坏或者留下脏状态尤其是数据库、消息队列这类中间件一定要给它一个“善后”的机会。4.2 信号处理实战优雅停服与强制杀进程的取舍我之前负责过一个 Java 服务有次线上发布时我先执行了kill PID结果进程没有立刻退出整整卡了快一分钟手里的发布脚本以为失败了直接接着执行了备份逻辑最后两个实例互相打架。后来我学乖了在脚本里先发 SIGTERM然后写一个循环去轮询进程是否还在超时后再考虑 SIGKILL。下面是一段可供参考的脚本逻辑#!/bin/bash PID$(pgrep -f myapp.jar | head -n1) if [ -z $PID ]; then echo 进程不存在 exit 0 fi kill $PID for i in $(seq 1 30); do if kill -0 $PID 2/dev/null; then sleep 1 else echo 进程已退出 exit 0 fi done echo 超时使用 SIGKILL kill -9 $PID这里kill -0 PID本身不会发任何实际信号它只是用来探测进程是否还活着。能执行成功说明进程存在执行失败说明进程已经没了。这个技巧在写进程管理脚本时非常实用可以安全地探测进程状态不用担心误伤。还有一个调优工具是优先级管理。nice -n -5 ./job.sh可以以更高优先级启动进程renice -n 5 -p PID可以调整运行中进程的优先级。优先级数字越小CPU 调度越优先范围是 -20 到 19普通用户只能调高降低优先级root 才能调低提高优先级。这个知识点比较冷门但面试时偶尔会考到。4.3 容易卡住的僵尸进程为什么 kill 不掉 Z 状态的 PID用ps aux看到 STAT 列是 Zzombie的进程时很多人第一反应是kill -9 PID结果发现怎么 kill 都没用。原因在于僵尸进程已经“死”了它不再执行任何代码也不响应任何信号只是在内核进程表里占了个位置等待父进程来回收它的退出状态。理解僵尸进程要从进程生命周期说起。Linux 中子进程退出后内核不会立刻删除它的进程描述符而是把它留在一个叫“僵尸状态”的中间态直到父进程调用wait()系统调用读取退出状态。如果父进程一直不调用 wait或者父进程本身退出了这些子进程就会被 init 进程或 systemd 收养并回收。所以僵尸进程一定有一个父进程但你不一定能通过 ps 找到它因为父进程可能已死它就成了孤儿被收养僵尸进程无法通过 kill 命令清除因为它已经没有可执行的上下文清除僵尸体质的唯一办法是让它的父进程退出或者让父进程正确回收子进程。排查时你可以用ps -eo pid,ppid,stat,cmd | grep Z找出所有僵尸进程及其 PPID然后看 PPID 是谁。如果是普通服务产生了大量僵尸子进程通常是这个服务代码里有 bug忘了处理子进程退出信号。一次性清理的办法是重启父进程父进程退出后僵尸子进程会被 init 或 systemd 重新收养并回收。4.4 前台后台切换与进程挂起的连锁反应这里再补一个和 PID 直接相关的实用场景。你在终端里启动了一个任务用 CtrlZ 把它暂停终端会显示类似[1] 已停止 job_sleep的信息同时会告诉你一个 job 编号。用jobs可以查看当前终端的任务列表bg %1让任务在后台运行fg %1把后台任务调回前台。这些操作背后其实都是信号在起作用CtrlZ 发送的是 SIGTSTPbg发送的是 SIGCONT。当你使用nohup启动一个进程并放到后台时它确实能脱离终端运行但要注意它仍然有 PID只是 PPID 会变成 1也就是被 init 系统收养了。很多人以为nohup command 之后进程就“没有 PID”了其实用pgrep -f command仍然能找到它。这个进程如果没被妥善管理就可能变成你下一个需要排查的“神秘占用”。5. 进程 PID 相关面试高频考点与避坑清单5.1 经典面试题如何查看进程、如何杀掉进程、如何查看端口占用结合相关热词里反复出现的“linux面试题”我专门整理一个高频考点清单。面试官问“在 Linux 上如何查看当前有哪些进程在运行”时你能答出ps -ef或ps aux是基础继续追问“怎么看某个进程的 PID”你能答出pgrep、pidof以及ps -ef | grep是进阶如果还追问“怎么通过端口找进程”那ss -tlnp和lsof -i :端口是标准答案再往下深挖“僵尸进程是什么、怎么处理”则考察你对进程生命周期是否真正理解。还有一个容易被问倒的题“kill -9和kill -15有什么区别”只答“一个强制一个优雅”还不够更深的理解是 SIGTERM 是通知进程自己退出进程可以在收到信号后执行清理逻辑SIGKILL 是由内核直接终止进程进程没有机会做任何善后。面试官接着问“为什么某些进程连 SIGKILL 都杀不掉”时你如果能答出“处于 D 状态的进程卡在不可中断的内核 I/O 上只能等待 I/O 返回或者重启系统”这就很加分。5.2 避免误杀和误判ps 结合 grep 的常见坑前面提过 grep 匹配到自身的问题实际中还有几个坑值得专门说。第一个坑是进程名匹配过于宽泛。比如你想查看 redis 的进程执行ps -ef | grep redis结果把包含 redis-cli、redis-server、redis-sentinel 的进程全部列出来了。如果只想匹配 redis-server最好使用pgrep -x redis-server或者ps -C redis-server -o pid,cmd其中-C参数按精确进程名过滤不会做子串匹配。第二个坑是同一个服务有多个进程PID 不是一个而是多个。比如 Nginx 启动后通常有一个 master 进程和多个 worker 进程pgrep nginx可能返回五六个 PID。有些脚本写得不严谨只取第一个 PID 进行操作结果把 master 杀了worker 变成孤儿最后整个服务处于半死不活状态。正确做法是关服务用kill $(cat /run/nginx.pid)这种主进程 PID或者用pkill nginx统一处理不要手动截取第一个。第三个坑是在脚本里使用ps却忘记了ww参数。命令行一长ps 输出就被截断grep -f匹配不到你需要的关键参数。根治办法是写脚本时优先用pgrep -f它对完整命令行匹配更友好而且不会因为终端宽度变化出现截断问题。5.3 进程 PID 在脚本中的可靠记录方式PID 文件的正确写法在项目里很多守护进程或定时脚本都会涉及“当前服务是否已在运行”的判断。常见的做法是启动时把自己的 PID 写入一个文件运行前读取这个文件判断旧进程是否存在。这样做的核心目的是为了防止同一个脚本被重复拉起导致资源竞争或状态错乱。一个比较稳妥的石英式写法如下#!/bin/bash PID_FILE/var/run/myjob.pid if [ -f $PID_FILE ]; then OLD_PID$(cat $PID_FILE) if kill -0 $OLD_PID 2/dev/null; then echo 任务已经在运行PID$OLD_PID exit 1 fi fi echo $$ $PID_FILE trap rm -f $PID_FILE EXIT # 模拟任务执行 while true; do sleep 10 done这段脚本里有两个细节第一kill -0用于探测旧进程是否存在避免读取到残留的 PID 文件时误判第二用trap在脚本退出时删除 PID 文件否则脚本意外退出会留下过期的 PID 文件。这些都是生产环境脚本里容易踩的坑写的时候要多留个心眼。5.4 与 ps 关联的系统工具top、htop、pstree 如何补充使用ps 给你的是某个时间点的静态快照如果想要动态观察进程占用的 CPU、内存变化就需要借助交互式工具。top命令大家都很熟进入 top 后按k可以输入 PID 杀进程按r可以调整优先级按M按键可以按内存占用排序按P按键可以按 CPU 占用排序。如果你知道具体 PID可以直接用top -p PID只观察这一个进程避免被其他进程干扰。htop是 top 的豪华版需要用包管理器安装但交互体验好了很多可以直接用 F5 看进程树用 F6 排序方向键选择一个进程后按 F9 发送信号。我个人在排查资源占用问题时习惯先用ps aux --sort-%cpu | head -20快速定位 CPU 最高的进程再用top -p PID长时间观察。pstree -p则是查父子关系的利器把一棵进程树完整画出来哪里出现了孤儿哪里进程堆积一目了然。5.5 容器环境中的 PID 使用差异与 namespace 概念最后想提醒一下容器环境的特殊情况。Docker 容器里运行ps -ef你只能看到容器内的进程看不到宿主机上的其他进程因为每个容器默认有独立的 PID namespace。也就是说你在容器里看到的 PID 123和宿主机上的 PID 123 完全不是同一个进程。这让“用 PID 管理进程”这件事在容器场景下多了不少麻烦。实际排查时如果发现在容器里kill PID无效先确认你是不是在正确的 namespace 里。容器内想退出当前进程可以直接用kill 1去触发应用进程退出因为容器内的 1 号进程通常是容器主进程但在宿主机上却不能用kill 1去乱动 systemd。多容器的环境中尽量避免在宿主机上盲目pkill某个进程名因为那可能同时杀掉多个容器里的同名进程。先docker ps确认容器名称和 ID再进入容器或者在宿主机上配合docker top查询是更安全的做法。还有一个相关知识点是 PID 复用。Linux 分配 PID 是有上限的默认值在/proc/sys/kernel/pid_max里可以看到一般为 32768 或更大。PID 用完后会被回收再分配所以一个旧 PID 文件里存着的数字过了一段时间后可能已经被分配给完全不相干的进程。这也是为什么写脚本时不能只校验“PID 文件是否存在”而要用kill -0或检查进程启动时间做二次确认。稳妥的脚本取数方式是把 PID 文件和进程启动时间一并记录判断时两者同时比对才能有效避免误判。
返回列表