拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux进程深度解析:状态、IPC与故障排查实战指南

Linux进程深度解析:状态、IPC与故障排查实战指南

1. 进程的本质:程序是静态的,进程是动态的

先从一个最容易绕进去的问题说起:进程到底是什么?很多教材开篇就说“进程是程序的一次执行过程”,这句话对,但说了等于没说。我更喜欢用做菜来类比:程序是你手机里的菜谱,躺在存储里不动,它就是个文件,有大小、有权限、有路径;进程是你照着菜谱在厨房里实际操作的那一整套流程,中间涉及切菜、热锅、放调料,这些动作是动态的、占用资源(煤气、水、锅铲)的、有生命周期(从备菜到出锅)的。程序只有一个,但你可以照着同一份菜谱做一百次菜,每开一次火,就是一个新进程。

拉回Linux环境里,程序就是磁盘上的可执行文件,比如/usr/bin/nginx;双击或者敲命令运行它,内核会为这次运行分配一个进程描述符(PCB)、独立的虚拟地址空间、文件描述符表、信号处理机制等一套完整“运行装备”,从此刻起,它才是进程。同一个二进制可以同时跑出多个进程,比如你机器上有8个nginx worker,它们的程序文件是同一个,但每个worker进程的PID、内存数据、打开的文件都各自独立,互不干扰。

1.1 进程的“身份证”:PCB(进程控制块)

Linux内核用task_struct结构体来管理进程,这个结构体就是进程的内核态“身份证”。它至少包含几类信息:进程标识符(PID、PPID)、进程状态、调度信息(优先级)、内存管理指针、文件系统信息(根目录、当前目录)、文件描述符表、信号处理信息、时间统计(CPU时间)、上下文数据(寄存器值、栈指针)等。

这里最关键的一点是:task_struct的存在让进程对内核而言是一个可以被调度、被挂起、被恢复的实体。CPU在执行某个进程时,会把它的上下文(寄存器、程序计数器等)加载进来;一旦时间片用完或者进程主动让出CPU,内核就负责把当前上下文保存回这个结构体,再把另一个进程的上下文装进来,这个过程叫上下文切换。理解这一点之后,你就知道为什么说“进程是资源分配的最小单位”了,因为PID、内存、文件表这些资源都挂在task_struct上。

另一个容易被忽略的点:task_struct本身不在进程自己的内存空间里,它分配在内核地址空间,对用户态程序是只读不可见的。换句话说,进程自己并不知道内核给它记了多少“小本本”,你可以在用户态用ps、top这些工具去“查看”它,但实际上你能看到的信息只是内核暴露给/proc文件系统的一小部分。

1.2 进程与线程:别再傻傻分不清

顺带把线程讲清楚,因为后台开发面试、日常排查几乎绕不开。Linux 早期其实没有独立的线程实现,线程就是进程——内核视角下,线程不过是共享了同一地址空间的“轻量级进程”(LWP)。这是 Linux 和其他操作系统设计上最不一样的点之一。

进程和线程的区别,我习惯从“私有”和“共享”角度来切:

  • 进程之间:地址空间、文件描述符表、信号处理器全部独立。一个进程崩溃,内核会回收它的资源,通常不影响其他进程。
  • 同一进程内的线程:共享代码段、全局数据、堆、打开的文件表;各自独立的只有栈、寄存器上下文、线程局部存储(TLS)。一个线程挂掉,如果触发的是SIGSEGV默认动作,整个进程都会挂掉,所有线程一起陪葬。

这就引出一个经典面试追问:“既然线程更轻量、切换更快,那为什么还要用多进程?”答案是隔离性。比如 Nginx 用多进程模型,一个 worker 崩了,master 会再拉起一个新的 worker,其他 worker 的活跃连接不受影响;而多线程模型里一个线程的段错误可以直接带走整个进程。再加上地址空间隔离带来的安全性,在多租户、强隔离场景下,多进程依然是无可替代的标配。

2. 进程的一生:从创建到退出,状态机全解

进程不是一成不变的,它从被创建那一刻起就在不停切换状态,直到退出。Linux 进程状态在ps里看是字母,在top里看也是字母,很多人背了R/S/D/T/Z/X但并不知道这些状态之间是怎么流转的。这部分值得花功夫精读。

2.1 进程状态:R、S、D、T、Z、X

我把这些状态用“一个人一天的活动”来映射:

  • R(Running/Runnable):进程正在 CPU 上运行,或者已经排队等待 CPU 调度。用大白话说,这个人要么正在干活,要么在候场区排队等着被叫号。
  • S(Sleeping,可中断):进程在等待某个条件(比如磁盘 IO 完成、网络数据到达、sleep()超时),可以被信号唤醒。这是最常见的状态,几乎所有与 IO 相关的进程长期处在这个状态。
  • D(Uninterruptible Sleep,不可中断睡眠):进程在内核态等待某种系统资源,不能被信号打断。最常见的场景是同步磁盘 IO。如果进程长期卡在 D 状态,通常意味着 IO 子系统出问题了,此时连kill -9都没用,因为信号根本传递不到它。
  • T(Stopped/Traced):进程被暂停。Ctrl+Z暂停前台任务、SIGSTOP停止进程、调试器断点命中,都会进 T 状态。可以用SIGCONT让它继续跑。
  • Z(Zombie,僵尸):进程已退出,但它的 PCB 还留在内核里等父进程来回收。这个状态很关键,后面单独讲。
  • X(Dead):进程真正被销毁,只是理论上存在这个状态,实际基本看不到。

如果系统出现大量 D 状态进程,优先检查磁盘健康度、NFS 挂载是否正常、IO 是否被打满。D 状态进程排不掉、杀不掉,唯一的办法是修好底层 IO 问题,让它们完成内核态操作,自然流转出去。

2.2 状态转换的关键场景

整个状态流转里最核心的一条主线是:创建 → 就绪 → 运行 → 阻塞 → 就绪 → 运行 → 退出。

用 fork 创建的进程,刚被创建时处于就绪态,等调度器选中它之后进入运行态;运行中只要遇到 IO 请求(比如读文件),就会主动让出 CPU,进入阻塞态,等 IO 完成后被唤醒,回到就绪态排队。这里有个反直觉的点:进程被阻塞之后,谁负责唤醒它?答案是中断和内核机制。比如磁盘中断来了,驱动会告诉内核这块数据准备好了,内核再去找对应进程的task_struct,把它从等待队列挪回就绪队列。理解了这条链路,你就能明白为什么 IO 密集型的进程状态栏里充满S了。

2.3 孤儿进程与僵尸进程:两个最经典的大坑

我自己早年排查过一个线上事故:一台服务器跑了几个月后负载没高,但 PID 用尽,无法创建新进程。最后定位发现是父进程没有调用wait(),导致大量子进程退出之后变成僵尸,把 PID 表吃满了。这个场景太典型了,几乎每个搞过服务端的人都会踩。

僵尸进程:子进程退出时,会向父进程发送SIGCHLD信号,但它的退出码、资源使用统计等信息需要父进程通过wait/waitpid来“收尸”。如果父进程没做这件事,子进程只能以 Z 状态残留在进程表里。僵尸进程不占用内存和 CPU,但它占着 PID,pid_max 默认 32768,一旦吃满,新进程就 fork 不出来了。

清僵尸的唯一正确姿势,是让它的父进程调用wait()。如果父进程已经死了,孤儿进程会被 init 进程(PID 1)收养,由 init 周期性地wait回收。但如果父进程活着又不收尸,谁都拿它没办法。

孤儿进程:父进程提前退出,子进程还在运行,内核会把子进程的父进程重新设为 PID 1(systemd 或 init),子进程变成孤儿进程。要注意“孤儿”不危险,危险的只是“没人给它收尸的僵尸”。

提示:实际写服务代码时,一定要在父进程里处理SIGCHLD信号并调用waitpid(-1, &status, WNOHANG),或者用双进程 + supervisor 模型让 1 号进程统一收养。别指望系统帮你自动清理僵尸,内核的默认机制就是“留给父进程决定”。

3. 管住进程:命令行实操与进程管理

概念讲完,直接上实操。这部分内容以命令行为主,但重点不是列出所有参数,而是把最常用、最核心的场景串起来,让你拿到一台陌生机器时能快速上手。

3.1 查看进程:ps、top、pidof、pgrep

ps -ef和ps aux是最常用的两条。前者用 System V 风格,后者用 BSD 风格,输出字段略有差异。我个人习惯直接用ps -ef --forest,加一个--forest参数可以看到进程之间的父子树状关系,排查“谁生出了谁”时非常好用。

要观察进程的实时性能,用top,但裸top的信息其实有点开盲盒的感觉。我更推荐用top -p PID1,PID2盯特定 PID,或者直接上htop(如果目标机器允许安装)。top输出里有一列S,即进程状态,配合%CPU、%MEM、TIME+可以判断进程是否异常:CPU 高不一定是坏事,但如果 TIME+ 疯涨且状态为 D,基本可以判定磁盘 IO 出了问题。

按名称找 PID 用pidof或者pgrep:

# 查看 nginx 的所有 PID pidof nginx # 按名字匹配,并且把进程名字也打出来 pgrep -a nginx # 按完整命令行匹配,比如精确匹配 php-fpm pgrep -f "php-fpm: master"

pgrep -f是按完整 command line 去匹配的,这个在排查脚本进程(比如一堆 python 脚本)时比单纯按进程名匹配准确得多。它的兄弟命令是pkill,工具名虽然带 kill,但pkill只是发信号,不是直接杀,默认发的是SIGTERM。

3.2 控制进程:kill、killall、pkill、nice/renice

很多人以为kill就是杀进程,其实kill的核心功能是“给进程发信号”,信号类型由参数指定,默认SIGTERM(15),可以优雅退出。

排查问题时的信号选择是基本功:

  • SIGTERM(15):请求进程主动退出。进程可以捕获这个信号做清理工作,比如关掉连接、写日志、保存状态。
  • SIGKILL(9):内核直接强制杀掉,进程无法捕获、无法忽略、无法做任何清理。这应该是最后手段,不要一上来就用。
  • SIGHUP(1):最初设计是挂断终端时通知进程,现在很多守护进程用它来重新加载配置文件(比如kill -HUP $(pidof nginx)做热重载)。
  • SIGSTOP(19)和SIGCONT(18):暂停/继续进程。这个组合在临时压测、紧急避险时很有用,不用杀进程就能让它“冻结”。

killall按名字杀,pkill支持正则匹配。举两个实际排查场景:

# 所有 java 进程全部优雅退出 killall -15 java # 杀掉命令行里带 service-mesh-proxy 的进程 pkill -f service-mesh-proxy

优先级控制也是进程管理的一部分。nice -n -10 ./heavy_task能以更高优先级启动,renice -n 5 -p PID可以修改运行中进程的优先级。注意普通用户只能调低优先级(增加 nice 值),调高需要 root 权限,这是防止恶意进程抢 CPU 的基本保护机制。

3.3 在代码中创建进程:fork、exec、wait

光会命令行不行,Linux 进程编程的三件套fork、exec、wait是理解进程概念的最佳实践入口。我见过太多人把 fork 背得滚瓜烂熟,但真让他手写一段代码就露馅。

fork一次调用,两次返回。这句话很抽象,拆开说:

#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); return 1; } if (pid == 0) { // 子进程进入这里,pid 为 0 printf("child process, pid=%d, parent pid=%d\n", getpid(), getppid()); } else { // 父进程进入这里,pid 是子进程的 PID printf("parent process, pid=%d, child pid=%d\n", getpid(), pid); } return 0; }

fork之后,子进程是父进程的完整副本,但它俩的地址空间已经互相隔离(写时复制技术,COW,Copy-on-Write),也就是说在 fork 之后的代码段里,它们只是从同一个“岔路口”分开走,之后各自修改的变量互不可见。

exec系列函数(execl、execv、execle、execve等)的作用是在当前进程空间加载并运行一个新的程序,替换掉当前进程镜像。最常见的组合是fork一个子进程,然后在子进程里exec执行其他程序,父进程用wait等待子进程结束。

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid = fork(); if (pid == 0) { // 子进程:替换成 ls 命令 execl("/bin/ls", "ls", "-l", "/tmp", NULL); perror("execl failed"); // 如果 exec 成功,这里不会执行 exit(1); } // 父进程:等待子进程退出 int status; waitpid(pid, &status, 0); printf("child exited with status %d\n", WEXITSTATUS(status)); return 0; }

这里有个新手极易踩的坑:exec系列函数的第一个参数path是要执行的程序,第二个参数是argv[0](进程名),这俩不一定是同一个字符串。很多人写成execl("/bin/ls", "/bin/ls", "-l", "/tmp", NULL);,也能跑,但argv[0]会变成/bin/ls,在一些依赖argv[0]做路径判断的程序里会出问题。

4. 进程间通信(IPC)精讲:管道、共享内存、消息队列

进程之间为什么要通信?原因很简单:每个进程有自己的独立地址空间,A 进程里的变量在 B 进程里根本不存在。想让两个进程协作,就必须通过内核提供的 IPC 机制交换数据。Linux 下的 IPC 方式不少,我按实际使用频次和重要性逐个拆。

4.1 管道:最基础、最常用的通信方式

管道分匿名管道和命名管道(FIFO)。匿名管道是最经典的形式,Shell 里天天在用:

ps -ef | grep nginx

这里的|就是匿名管道:左边ps -ef的标准输出接到右边grep nginx的标准输入。匿名管道是半双工的,数据只能单向流动,且只在有血缘关系的进程之间使用(父子进程、兄弟进程),因为管道本身没有名字,只能靠 fork 时继承文件描述符才能共享到管道的读写端。

命名管道(FIFO)解决了“没有血缘关系也能通信”的问题,它会在文件系统里创建一个特殊的管道文件:

# 终端1:创建 FIFO 并写入 mkfifo /tmp/myfifo echo "hello" > /tmp/myfifo # 终端2:读取 FIFO cat /tmp/myfifo

你会发现终端1在写入时会阻塞,直到终端2打开 FIFO 并读走数据为止。这个阻塞特性让 FIFO 天生适合做进程间的同步,但也容易造成“写端没人读,写进程卡死”的问题。实际写代码时建议设置非阻塞标志,或者用select/poll处理管道读写,避免永久阻塞。

管道在内核里是一个环形缓冲区,Linux 上默认大小一般是 64KB(可以查/proc/sys/fs/pipe-max-size)。写入超过缓冲区容量时,写进程会被阻塞,直到读端消费掉一部分。这个才是管道最核心的语义:背压。它能自然限制生产速度超过消费速度的问题,不需要你额外做流控。

4.2 消息队列:需要时就上,不需要就别硬上

System V 消息队列和 POSIX 消息队列的本质,都是内核维护的一个链表,每个节点是一条消息,消息有 type 和 data 两部分。发送方通过msgsnd发消息,接收方通过msgrcv按 type 取消息,可以做到“发送方和接收方在时间上解耦”。

我个人的看法是:除非你的需求非常明确(需要按消息类型分类接收、需要内核级持久化),否则在常规应用开发里,消息队列优先选用户态中间件(比如 Redis Stream、Kafka、RabbitMQ),而不是直接调 System V 接口。原因在于内核消息队列的生命周期管理相当麻烦,消息队列不会自动释放,进程崩溃之后残留的队列需要你用ipcrm手动清理,我第一次遇到 IPC 资源泄漏时排查到凌晨两点,最后发现是以往测试残留的几千条消息队列,用ipcs -q一看,全是孤魂野鬼。

4.3 共享内存与信号量:高性能场景的黄金搭档

共享内存是速度最快的 IPC 方式,因为数据不需要从用户态复制到内核态再复制回用户态,而是两个进程直接映射同一段物理内存。

System V 共享内存的使用套路大体是这样的:

#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> int main() { // 1. 创建/获取共享内存段 int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); if (shmid < 0) { perror("shmget failed"); return 1; } // 2. 映射到自己的地址空间 char *ptr = (char *)shmat(shmid, NULL, 0); if (ptr == (char *)-1) { perror("shmat failed"); return 1; } // 3. 写入数据 sprintf(ptr, "hello from shared memory"); // 4. 解除映射并删除共享内存 shmdt(ptr); shmctl(shmid, IPC_RMID, NULL); return 0; }

共享内存的问题在于:它本身不带同步机制。两个进程同时写同一块共享内存,数据就会互相覆盖。解决方案就是在共享内存之外再加一套信号量(Semaphore)做互斥,或者用原子操作。

这里提一个性能敏感项目里常用的技巧:如果你需要传输“大量结构化数据”,共享内存 + 无锁队列(基于 CAS 实现)的吞吐量可以轻松超过管道几个数量级。我参与过一个量化交易回测系统,最初用消息队列做行情分发,事件延迟平均在 50 微秒左右;改用共享内存 + 无锁环形队列之后,延迟降到 3 微秒以内,吞吐量直接翻了一个量级。但是,这套方案的复杂度也是成倍上升,代码要处理内存屏障、缓存行对齐、ABA 问题,非性能瓶颈场景不建议轻易碰。

4.4 信号、Socket 与其他:按场景选型

上面这些还不够。信号(Signal)本质是一种异步事件通知机制,不能传大数据,但适合做“控制”而不适合做“数据”。SIGINT(Ctrl+C)、SIGTERM、SIGUSR1/SIGUSR2(用户自定义)都是典型的信号应用场景。

Socket 则是跨主机、跨网络通信的神器,UNIX Domain Socket 在同主机进程间通信时比 TCP loopback 更快,而且支持流式(SOCK_STREAM)和数据报(SOCK_DGRAM)两种模式,很多数据库和中间件都在用。你会看到 Nginx、Redis、PostgreSQL 在 Linux 上默认都支持通过 UNIX Socket 对外提供服务,就是因为它在本地通信时的性能优势非常明显。

IPC 选型我总结了一个基于实际经验的参考:

通信方式速度数据量使用复杂度适用场景
匿名管道中小低父子进程间流式传输,Shell 管道
命名管道(FIFO)中小中无血缘关系的进程间按序通信
消息队列中小中需要按类型分类消费的简单场景
信号高极小低控制、通知、中断处理
共享内存极高大高高性能大规模数据传输
信号量高无中多进程互斥与同步
Socket中高大中跨主机/跨协议通信,通用性最强

5. 常见问题与排查技巧实录

写代码的人没有不踩进程相关坑的。下面这些是我实际排查过的问题,每一个都是血泪换来的经验,按“现象 → 排查 → 解决”的方式列出来,遇到问题直接对号入座。

5.1 僵尸进程堆积,PID 不够用

现象:ps -ef刷出来一堆[php-fpm] <defunct>,系统日志报fork: Cannot allocate memory(注意这并不一定是内存真的不够,而是 PID 用完了)。

排查:先ps -eo pid,ppid,stat,cmd | grep defunct统计僵尸进程数量和它们的 PPID。僵尸进程的关键信息是 PPID,这个才是需要“负责”的对象。

解决:如果僵尸的父进程是 PHP-FPM、Nginx 之类的主进程,可以通过kill -HUP <parent_pid>让它们平滑重载并回收子进程;如果是我方自研服务,必须修代码主动waitpid。实在没办法临时救急的,可以echo N > /proc/sys/kernel/pid_max调大 PID 上限,但这是治标不治本。

5.2 端口被占用,找不到进程

现象:启动服务时报Address already in use,或者用lsof -i:8080找不到对应进程。

排查:先ss -lntp | grep 8080看监听端口的进程,这是现代 Linux 上最推荐的工具,ss比netstat更快、更全。如果ss显示不出来,多半是进程把端口绑定到了具体 IP 而不是0.0.0.0,这时候用ss -lntp | grep <IP>:8080去精确匹配。

解决:确认这个端口能杀之后,fuser -k 8080/tcp可以一键杀掉占用端口的进程,比先查 PID 再 kill 快得多。如果端口杀不掉,用ps -eo pid,ppid,stat,cmd | grep <PID>检查进程状态,如果卡在 D 状态,问题大概率出在底层 IO。

5.3 dpkg 前端锁问题:同源不同坑

现象:Ubuntu/Debian 上执行apt-get install报错dpkg: 错误: 另外一个进程已经为 dpkg 前端锁 加锁。

排查:先确认是不是真的有一个 apt 进程在跑:

ps aux | grep -E "apt|dpkg"

如果是长时间卡住的残留进程,可以直接杀掉,然后清理锁文件:

sudo killall -9 apt apt-get dpkg sudo rm -f /var/lib/dpkg/lock-frontend sudo rm -f /var/lib/dpkg/lock sudo rm -f /var/cache/apt/archives/lock sudo dpkg --configure -a

注意:rm -f /var/lib/dpkg/lock*操作前一定要先确认没有apt/dpkg相关进程在跑,否则可能损坏 dpkg 数据库。这是我最常看到别人踩的坑。

5.4 进程异常高 CPU/内存,不知道是谁干的

现象:机器卡顿,top显示某个进程 CPU 占用持续 100%+,或者内存狂涨不回落。

排查思路按“应用层 → 系统层 → 内核层”的顺序推进:

先用top -o %CPU按 CPU 排序,top -o %MEM按内存排序,锁定嫌疑进程。再刷ps -eo pid,ppid,etime,time,cmd看进程的启动时间和累计 CPU 时间。如果etime(运行时长)只有几秒但累计 CPU 时间很高,说明进程一直在循环空转;如果内存持续上涨但 RSS 不释放,有可能存在内存泄漏。

拿到 PID 之后,有两个强力工具可以进一步定位:

# 查看进程打开的文件和 socket lsof -p PID # 实时跟踪进程系统调用 strace -p PID -e trace=read,write,open,close

strace能看到进程在内核层面的系统调用序列,对于 CPU 飙高的死循环、卡在某个前提条件上的逻辑错误,效果非常直观。比如我排查过一个 CPU 100% 的 Lua 脚本,strace显示它每秒几万次gettimeofday()调用,最后定位到代码里用os.time()做了一个没有 sleep 的轮询循环,这就是典型的“应用层写法埋雷”。

C/C++ 内存问题可以用valgrind跑,但线上环境太重,我一般用gdb -p PID做现场分析,配合/proc/PID/smaps查看进程虚拟内存分布情况。如果/proc/PID/smaps里某个匿名映射区域特别大且持续增长,基本可以判定是堆内存泄漏,方向有了,剩下的就是对业务代码逐段筛查了。

5.5 进程无法杀掉:D 状态和内核驱动的锅

现象:kill -9 PID毫无反应,进程仍然在那里。

排查:先看状态。如果是D(不可中断睡眠),信号在进程处于内核态 IO 时根本排不上队,你杀几十次都没用。这种时候用ps -eo pid,ppid,stat,wchan:32,cmd看wchan字段,它代表进程阻塞在哪个内核函数上,比如经常出现wait_on_page_bit、kjournald之类,基本可以锁定是文件系统/块设备层的 IO 卡住了。

解决思路不是“杀进程”,而是“修 IO”:

  • 检查挂载的 NFS/Ceph 等网络文件系统是否连接断了。
  • 检查磁盘是否有坏道、RAID 是否降级、虚拟机底层存储是否抖动。
  • 如果是迅雷一类的下载进程频繁触发D,大概率是磁盘长期负载过高,优先做磁盘性能调优和 IO 调度优化。

内核态卡死的极端情况,只有重启或等待 IO 超时。所以生产环境的经验是:一旦涉及网络存储,IO 超时参数必须配短一点,避免进程一卡就是十分钟。

6. 一些从实战中沉淀下来的体会

关于进程这块,市面上资料极为泛滥,但多数人卡在“背了概念但不会用”这个关口。我把自己最常对团队说的三句话放这里:

第一,遇到进程异常,先看状态,再谈杀不杀。R、S、D、Z、T五种状态背后的原因截然不同,不搞清楚状态就直接kill -9,大概率治标不治本,甚至把服务彻底搞挂。

第二,写多进程的服务端代码时,永远把僵尸进程问题放在第一位,子进程退出后的waitpid逻辑应该在fork之前就写进设计文档里。这个问题的隐蔽之处在于:它不是立刻爆发的,而是在系统运行几个月后、PID 耗尽的那一刻才以最难看的方式呈现。

第三,排查进程问题的核心是收集信息,而不是瞎操作。ps -eo、ss -lntp、lsof -p、strace -p、/proc/PID/*这五个工具用熟了,至少能解决九成以上进程相关的线上故障。

最后再分享一个小技巧:我电脑上长期存了一个 alias,偶尔就能救一命。

alias psgrep='ps -eo pid,ppid,user,stat,etime,time,cmd | grep -v grep | grep --color=auto'

别小看这个命令组合,它把最关键的 PID、PPID、状态、运行时长、累计 CPU 时间、完整命令行一次列全,查问题时少打十几条命令。真希望刚入行的时候有人告诉我这些,能少走很多弯路。

返回列表