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盯实时,然后翻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和线程状态分析,大部分问题都能找到答案。