12、进程与线程调试:ps命令、top命令、proc文件系统、线程状态分析

进程和线程,说白了就是Android系统里最活跃的「小人物」。你系统卡不卡、App会不会死、某个后台服务是不是在偷吃CPU,全看它们的状态。我调试过上百台设备,每次遇到诡异问题,第一反应就是——先看看进程和线程在干嘛。

今天咱们就把这块彻底聊透。从最基础的ps命令,到深入proc文件系统,再到分析线程状态,一条线串下来。

12.1 ps命令:进程快照的「第一眼」

ps命令是我最常用的调试工具,没有之一。它就像给系统拍了一张快照,告诉你此时此刻所有进程的「身份证」信息。

基本用法很简单:

adb shell ps

输出大概长这样:

USER      PID   PPID  VSIZE  RSS   WCHAN    PC         NAME
u0_a123   1234  567   2145678 123456 SyS_epoll_ 00000000 S com.example.app
system    567   1     1234567 98765 binder_io 00000000 S system_server
root      1     0     123456  23456 do_epoll_ 00000000 S init

每一列的含义,我简单解释一下:

  • USER:进程所属的用户。App进程通常是u0_a开头的,系统进程是system或root。
  • PID:进程ID,唯一标识。
  • PPID:父进程ID。比如App的父进程一般是zygote。
  • VSIZE:虚拟内存大小,单位是KB。这个值通常很大,别被吓到。
  • RSS:实际物理内存占用,单位也是KB。这个才是真正占用的内存。
  • WCHAN:进程当前在内核中的等待状态。比如SyS_epoll_表示在等待epoll事件。
  • PC:程序计数器,当前执行的指令地址。
  • NAME:进程名。

小技巧:我个人习惯用 ps -A 来查看所有进程,包括内核线程。普通ps只会显示用户空间进程。

有一次我遇到一个App莫名其妙地频繁重启,用ps一看,发现它的PPID不是zygote,而是某个奇怪的系统服务。顺藤摸瓜,最后发现是那个服务在搞鬼。

12.2 top命令:实时监控CPU和内存

ps是静态的,top是动态的。它每隔几秒刷新一次,告诉你当前谁在吃CPU、谁在占内存。

adb shell top

输出示例:

Tasks: 123 total, 2 running, 121 sleeping
Mem: 1234567k total, 987654k used, 246913k free
Swap: 0k total, 0k used, 0k free

PID   PR   CPU%  S   #THR   VSS    RSS    NAME
1234  20   45%   R   12     2145678 123456 com.example.app
567   18   12%   S   56     1234567 98765 system_server

关键字段:

  • CPU%:CPU占用百分比。超过50%就要警惕了。
  • S:进程状态。R是运行,S是睡眠,D是不可中断睡眠。
  • #THR:线程数。一个进程的线程数太多,往往意味着有问题。

注意:top命令在Android上默认显示的是CPU使用率,不是CPU时间。如果你看到某个进程CPU%超过100%,别慌——那说明它跑在多核上。

我曾经遇到一个案例:某款手机玩游戏时发热严重。用top一看,发现有个系统服务CPU%一直飙在80%以上,而且线程数高达200多个。后来定位到是某个驱动模块的线程泄漏了。

12.3 proc文件系统:进程的「病历本」

proc文件系统是Linux内核暴露给用户空间的一个接口。每个进程在/proc/[PID]目录下都有一堆文件,记录了它的所有信息。

常用的几个文件:

文件路径 内容说明
/proc/[PID]/status 进程状态、内存、线程数等基本信息
/proc/[PID]/maps 内存映射信息,看哪些库被加载了
/proc/[PID]/fd/ 文件描述符,看打开了哪些文件
/proc/[PID]/task/ 线程目录,每个子目录对应一个线程
/proc/[PID]/wchan 进程在内核中的等待原因

举个例子,查看某个进程的线程数:

adb shell cat /proc/1234/status | grep Threads
Threads: 12

或者查看它打开了哪些文件:

adb shell ls -l /proc/1234/fd/

如果看到某个进程打开了成千上万个文件描述符,那基本可以断定是文件泄漏了。

核心思路:proc文件系统是调试的「最后一公里」。当ps和top给不了你足够信息时,直接去翻proc目录,往往能找到真相。

12.4 线程状态分析:从「卡死」到「活蹦乱跳」

线程状态是调试多线程问题的关键。Android里的线程状态,说白了就是以下几种:

  • Running (R):正在CPU上跑。如果长时间处于R状态,说明它在疯狂计算。
  • Sleeping (S):可中断睡眠。大部分线程都在这个状态,等待事件唤醒。
  • Uninterruptible Sleep (D):不可中断睡眠。通常是在等待I/O。如果大量线程卡在D状态,说明磁盘或网络有问题。
  • Zombie (Z):僵尸进程。子进程已经结束,但父进程还没收尸。这种线程不占CPU,但会泄漏资源。
  • Stopped (T):被暂停。通常是被调试器或信号暂停了。

查看线程状态,最直接的方法是:

adb shell ps -T -p [PID]

或者直接看proc里的task目录:

adb shell ls /proc/1234/task/

每个线程ID对应一个子目录,里面也有status文件。

举个例子,我曾经调试过一个App启动慢的问题。用ps -T一看,发现主线程在等一个锁,而那个锁被一个后台线程占着。后台线程又在等网络I/O,结果整个App就卡住了。说白了,就是典型的死锁场景。

我的经验:遇到线程卡死,先看主线程在等什么。用 cat /proc/[PID]/task/[TID]/wchan 可以知道它在内核里等什么。如果是futex,那多半是锁竞争。

12.5 实战:用一张图串起所有知识点

下面这张图,是我自己总结的进程线程调试流程。每次遇到问题,我就按这个思路走一遍:

进程与线程调试流程 遇到问题(卡顿/死机/泄漏) 第一步:ps命令 → 获取进程快照 第二步:top命令 → 实时监控CPU/内存 第三步:proc文件系统 → 深入分析细节 第四步:线程状态分析 → 定位根因 查看PID、PPID、RSS 查看CPU%、线程数、状态 查看status、maps、fd、wchan 区分R/S/D/Z/T状态

这张图的核心逻辑是:从粗到细,从外到内。先用ps看全局,再用top盯实时,然后翻proc找细节,最后分析线程状态定根因。每一步都有它的价值,缺一不可。

12.6 避坑指南

最后分享几个我踩过的坑:

  • 别只看CPU%:有时候CPU%不高,但线程卡在D状态,系统一样会卡。我遇到过磁盘I/O瓶颈,CPU%才10%,但系统已经慢得像蜗牛。
  • 线程数不是越多越好:一个进程的线程数超过100,基本可以认定有问题。线程切换开销很大,而且容易导致锁竞争。
  • proc文件系统里的数字是实时的:但有些文件(比如maps)内容很大,读取时会影响性能。生产环境慎用。
  • ps -T 看到的线程ID是TID,不是PID:别搞混了。TID是线程ID,PID是进程ID。主线程的TID等于PID。

嗯,以上就是进程与线程调试的核心内容。记住,调试不是玄学,是一步一步推理的过程。用好ps、top、proc和线程状态分析,大部分问题都能找到答案。


专注资料整理