Linux ps 命令深度解析:从进程状态到性能监控的实现原理

5 阅读4分钟

ps 命令是最常用的工具之一。但很多人只知道 ps aux,却不了解背后的实现原理。今天我们来深入解析这个命令。

ps 命令的核心:读取 /proc 文件系统

ps 命令并不直接调用系统 API,而是读取 /proc 虚拟文件系统:

# ps 命令的本质就是读取这些文件
ls /proc/1234/
# cmdlin  comm  cwd  exe  fd  maps  stat  status  ...

每个进程在 /proc 下都有一个以 PID 命名的目录,里面有各种文件记录进程 信息 :

  • cmdline: 命令行参数(以 null 分隔)
  • comm: 进程名称
  • stat: 进程状态信息(机器可读)
  • status: 进程状态信息(人类可读)
  • fd/: 打开的文件描述符目录
  • exe: 指向可执行文件的符号链接

理解 ps aux 输出的每一列

ps aux
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root         1  0.0  0.1 169424 11200 ?        Ss   May08   0:05 /sbin/init

关键字段详解

VSZ (Virtual Memory Size)

  • 进程虚拟内存大小(KB)
  • 包含堆、栈、共享库、未分配内存
  • 数值通常很大,但不代表实际占用

RSS (Resident Set Size)

  • 实际驻留在物理内存的大小(KB)
  • 不包含 Swap 中的内存
  • 真正消耗的物理内存

STAT (进程状态)

  • R: Running(运行中或就绪)
  • S: Sleeping(可中断睡眠,等待事件)
  • D: Disk sleep(不可中断睡眠,通常在等 I/O)
  • Z: Zombie(僵尸进程,已终止但父进程未回收)
  • T: Stopped(暂停状态)

状态后面的修饰符:

  • +: 前台进程组
  • -: 会话领导者
  • l: 多线程进程
  • <: 高优先级进程
  • N: 低优先级进程
  • s: 会话领导者

%CPU 的计算原理

ps 计算 CPU 使用率的公式:

%CPU = (进程总 CPU 时间 / 进程运行总时间) * 100

但这有个陷阱:ps aux 显示的是进程启动以来的平均 CPU 使用率,不是实时值!

一个进程启动后跑了 1 秒 CPU,然后休眠 1 小时,ps 显示的 %CPU 会非常低。

要查看实时 CPU 使用率,需要用 toppidstat

实战案例:定位高 CPU 进程

案例 1: 找出 CPU 占用最高的进程

# --sort=-%cpu 按CPU降序排列
ps aux --sort=-%cpu | head -10

# 输出
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# mysql    10234 78.5 15.2 4523124 1.2g ?        Sl   May08 123:45 /usr/sbin/mysqld

案例 2: 查看进程的线程信息

# -L 显示线程,LWP 是线程 ID
ps -Lp 10234

# PID   LWP   TTY      STAT  TIME COMMAND
# 10234 10234 ?        Sl    0:05 mysqld
# 10234 10235 ?        Sl    0:12 mysqld
# 10234 10236 ?        Sl    0:08 mysqld

LWP (Light Weight Process) 就是线程 ID,在 Linux 中线程本质上是轻量级进程。

案例 3: 查看进程树关系

# --forest 显示父子进程关系
ps auxf

# 或用 pstree 命令
pstree -p 10234

案例 4: 找出僵尸进程

# 查找状态为 Z 的进程
ps aux | awk '$8 ~ /Z/ {print}'

# 输出
# user  12345  0.0  0.0      0     0 pts/0    Z+   10:23   0:00 [python] <defunct>

僵尸进程的 CMD 会显示 <defunct>

ps vs top vs htop 的区别

工具特点适用场景
ps快照式,一次查询进程信息查询、脚本统计
top实时刷新,交互式实时监控、动态观察
htop彩色界面,鼠标操作友好的实时监控

性能差异:

  • ps aux 扫描所有进程,约 10-50ms
  • top 每秒刷新,持续占用 CPU
  • htop 比 top 更耗资源(彩色渲染、更多计算)

高级技巧

1. 自定义输出格式

# -o 指定显示的列
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem

# PID  PPID CMD                         %MEM %CPU
# 1234     1 /usr/sbin/mysqld            15.2 78.5
# 5678     1 /usr/bin/dockerd             8.3 12.4

2. 查看进程打开的文件

# 查看进程 1234 打开的所有文件
ls -l /proc/1234/fd/

# 或用 lsof 命令
lsof -p 1234

3. 查看进程的环境变量

# 进程启动时的环境变量
cat /proc/1234/environ | tr '\0' '\n'

4. 查看进程的内存映射

cat /proc/1234/maps

# 输出格式
# 地址范围           权限  偏移     设备   inode   路径
# 00400000-0040b000 r-xp 00000000 08:01 262210  /usr/bin/ps
# 0060a000-0060b000 r--p 0000a000 08:01 262210  /usr/bin/ps

常见陷阱

1.僵尸进程无法 kill

kill -9 12345  # 对僵尸进程无效

僵尸进程已经终止,kill -9 无效。正确做法是:

  1. 找到父进程: ps -ef | grep 12345
  2. 重启或修复父进程,让它调用 wait() 回收子进程

2. VSZ 不等于实际内存占用

ps aux | grep mysql
# VSZ 4523124 (4.3GB)
# RSS 1258291 (1.2GB)  <- 这才是真实占用

3. 进程状态 D 的进程无法 中断

ps aux | awk '$8 ~ /D/'
# 处于 D 状态的进程通常在等 NFS、磁盘 I/O
# kill -9 也无法杀死,只能等待 I/O 完成

Web 实现:浏览器端的进程监控

虽然浏览器无法直接访问 /proc,但可以通过 API 转发:

// 后端 API: /api/processes
export async function GET() {
  const fs = require('fs')
  const processes = []

  // 读取 /proc 目录下的所有数字目录(进程)
  const pids = fs.readdirSync('/proc').filter(d => /^\d+$/.test(d))

  for (const pid of pids) {
    try {
      const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8')
      const comm = fs.readFileSync(`/proc/${pid}/comm`, 'utf-8').trim()

      // 解析 stat 文件(格式复杂,用空格分割)
      const parts = stat.split(' ')
      const utime = parseInt(parts[13])  // 用户态时间
      const stime = parseInt(parts[14])  // 内核态时间

      processes.push({
        pid: parseInt(pid),
        name: comm,
        utime: utime,
        stime: stime,
        state: parts[2]  // 进程状态
      })
    } catch (e) {
      // 进程可能已退出
    }
  }

  return Response.json(processes)
}

前端展示:

function ProcessList() {
  const [processes, setProcesses] = useState([])

  useEffect(() => {
    const interval = setInterval(async () => {
      const res = await fetch('/api/processes')
      const data = await res.json()
      setProcesses(data)
    }, 1000)

    return () => clearInterval(interval)
  }, [])

  return (
    <table>
      <thead>
        <tr>
          <th>PID</th>
          <th>Name</th>
          <th>State</th>
          <th>CPU Time</th>
        </tr>
      </thead>
      <tbody>
        {processes.map(p => (
          <tr key={p.pid}>
            <td>{p.pid}</td>
            <td>{p.name}</td>
            <td>{p.state}</td>
            <td>{p.utime + p.stime}</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

总结

ps 命令看似简单,实则蕴含了 Linux 进程管理的核心知识:

  1. 数据来源: /proc 虚拟文件系统
  2. 关键字段: VSZ(虚拟)、RSS(真实)、STAT(状态)
  3. 性能指标: %CPU 是平均值,非实时值
  4. 高级用法: 自定义格式、排序、线程查看
  5. 常见陷阱: 僵尸进程无法 kill、D 状态进程不可中断

掌握 ps 命令,是 Linux 性能排查的基础。


相关工具: