前言
在Linux系统中,进程状态是系统管理和性能调优的核心知识。一个进程从诞生到终止,会经历运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)等多种状态。理解这些状态的含义、触发条件及观察方法,是诊断进程挂起、资源泄漏等问题的关键。
目录
1、运行状态(R - Running/TASK_RUNNING)
2、可中断睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)
3、不可中断睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE)
4、停止状态(T - Stopped/TASK_STOPPED)
6、僵尸状态(Z - Zombie/EXIT_ZOMBIE)
一、进程状态观察详解
1、运行状态(R - Running/TASK_RUNNING)
进程处于运行状态(running)并不等同于正在执行。运行状态意味着该进程要么正在CPU上运行,要么处于运行队列中等待执行。因此,系统中可以同时存在多个R状态的进程。
重点:
- 所有可调度的运行状态进程都会被放入运行队列
- 操作系统进行进程切换时,直接从运行队列中选择下一个要执行的进程
- 单核CPU上同一时刻只有一个进程真正在CPU上运行
其他R状态的进程都在运行队列中等待
多核CPU上可以有多个进程同时处于"正在运行"状态
示例观察方法
1. 创建测试程序
创建一个简单的CPU密集型程序来观察R状态:
// cpu_busy.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
printf("PID: %d\n", getpid());
while(1) {
// 空循环保持CPU占用
}
return 0;
}
编译:
gcc cpu_busy.c -o cpu_busy
2. 运行程序并观察状态
方法一:使用top命令
在当前终端窗口运行程序:
./cpu_busy
在另一个终端窗口运行top:
top
在top界面中,按Shift + P
按CPU使用排序、观察STAT
列,会显示R
状态:
方法二:使用ps命令
ps aux | grep cpu_busy
输出示例:
其中R+
表示:
R
:运行状态+
:在前台进程组中
2、可中断睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)
一个进程处于浅度睡眠状态(sleeping)表示它正在等待某个事件的完成。处于这种状态的进程可以被随时唤醒或终止(这种状态也被称为可中断睡眠,即interruptible sleep)。
例如运行以下代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("I am running... \n");
sleep(100);
return 0;
}
编译并运行:
在代码中调用sleep函数休眠100秒期间,若用其他用户查看进程状态,会发现进程处于浅度睡眠状态:
处于浅度睡眠状态的进程可以被终止,使用kill命令即可强制结束该进程:
3、不可中断睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE)
当进程处于深度睡眠状态(Disk Sleep)时,系统无法终止该进程,只能等待其自行唤醒。这种状态也被称为不可中断睡眠状态(Uninterruptible Sleep),通常发生在进程等待I/O操作完成时。
以磁盘写入为例:当进程发起写盘请求后,会进入深度睡眠状态等待磁盘响应(如写入成功/失败的通知)。在此状态下,即使系统也无法终止该进程,必须等待I/O操作完成。(又称磁盘休眠状态)
方法一:模拟磁盘I/O导致的D状态
1. 创建测试脚本
创建一个会触发磁盘I/O的脚本:
#!/bin/bash
# 保存为 disk_io_test.sh
echo "PID: $$"
echo "将模拟磁盘I/O操作..."
# 向慢速设备(如U盘/NFS)写入大量数据
dd if=/dev/zero of=/mnt/nfs/testfile bs=1M count=1024 2>/dev/null
echo "I/O操作完成"
2. 准备观察环境
第一步:查看可用设备
lsblk # 查看所有存储设备
输出示例:
根据lsblk
的输出,可以看到我的系统只有一个虚拟磁盘设备(vda),没有检测到其他存储设备。这是典型云服务器或虚拟机的配置。
vda:虚拟磁盘(40GB)
vda1:第一个分区(已挂载到根目录
/
)
第二步:创建挂载点目录
sudo mkdir /mnt/mydisk # 创建一个新目录作为挂载点
第三步:执行挂载一个慢速存储设备
sudo mount /dev/vda1 /mnt/mydisk
第四步:验证挂载
df -h # 查看已挂载的文件系统
或
ls /mnt/mydisk # 查看挂载的设备内容
第五步:给脚本执行权限:
chmod +x disk_io_test.sh
第六步:运行并观察
在一个终端运行脚本:
./disk_io_test.sh
在另一个终端观察进程状态:
watch -n 0.5 'ps aux | grep disk_io_test | grep -v grep'
命令刚启动时的状态,可能还未进入深度睡眠(D状态),而如果进入深度睡眠(D状态),会输出示例,必须要有产生足够的 I/O 压力,才会实现(我模拟不了,大概看一下就行):
user 12345 0.0 0.0 12345 678 pts/1 D+ 14:30 0:05 /bin/bash ./disk_io_test.sh
其中D+
表示:
D
:深度睡眠状态(不可中断)+
:前台进程组
4、停止状态(T - Stopped/TASK_STOPPED)
Linux系统可通过发送SIGSTOP信号将进程挂起为暂停状态(Stopped),发送SIGCONT信号则可恢复被暂停的进程运行。
例如:
向某进程发送SIGSTOP信号后,该进程立即进入暂停状态:
直到收到SIGCONT信号才会继续执行:
5、跟踪状态(T - Tracing Stop)
1. 创建进程并进入跟踪状态
首先,我们需要创建一个可以被跟踪的进程:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("子进程PID: %d\n", getpid());
while(1) {
// 无限循环保持进程运行
sleep(1);
}
return 0;
}
编译并运行这个程序,记下它的PID:
2. 使用调试器附加到进程
在另一个终端中,使用gdb附加到该进程:
sudo gdb -p <PID>
使用第三个终端观察,此时,被调试的进程会进入T (Tracing Stop)状态:
3. 观察进程状态
在第三个终端中,查看进程状态:
ps -o pid,state,cmd -p <PID>
输出可能类似于:
4. 状态变化示例
初始状态:进程正常运行,状态为"S"(睡眠)
附加调试器后:状态变为"T"(被跟踪)
继续执行:在gdb中输入"continue",状态可能变回"S"
断点命中:当遇到断点时,状态再次变为"T"
5. 其他产生T状态的场景(了解,不要求掌握)
使用strace跟踪系统调用:
strace -p <PID>
使用ptrace系统调用:Ptrace 详解 - tangr206 - 博客园
// 跟踪进程的示例代码 ptrace(PTRACE_ATTACH, pid, NULL, NULL);
接收到SIGSTOP信号:
kill -SIGSTOP <PID>
6. 如何解除T状态
在gdb中:使用
detach
命令或退出gdb对于strace:按Ctrl+C终止strace
对于SIGSTOP:发送SIGCONT信号
kill -SIGCONT <PID>
6、僵尸状态(Z - Zombie/EXIT_ZOMBIE)
1. 创建僵尸进程的C程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0)
{
int cnt=60;
while(cnt--)
{
// 子进程
printf("子进程 PID: %d 开始运行\n", getpid());
sleep(2); // 模拟子进程工作
printf("子进程 PID: %d 即将退出\n", getpid());
}
exit(0); // 子进程退出
} else if (pid > 0) {
// 父进程
printf("父进程 PID: %d 创建了子进程 %d\n", getpid(), pid);
printf("父进程将不调用wait(),故意制造僵尸进程\n");
// 父进程不调用wait(),继续运行
while(1) {
sleep(1);
printf("父进程仍在运行...\n");
}
} else {
perror("fork失败");
exit(1);
}
return 0;
}
2. 编译并运行程序
gcc -o zombie zombie.c
./zombie
3. 观察进程状态
重新运行,然后在另一个终端窗口中,使用以下命令观察进程状态变化:
watch -n 1 'ps -eo pid,ppid,state,cmd | grep -E "PID|zombie"'
4. 预期观察到的现象
初始阶段:父子进程都处于运行状态(S)
子进程退出后:
子进程状态变为Z(僵尸)
父进程仍在运行
最终状态:
5. 清理僵尸进程(后面会讲解,现在先了解)
要清理僵尸进程,可以:
终止父进程:
kill -9 <父进程PID>
或者修改程序让父进程调用wait():
// 在父进程代码中添加 wait(NULL); // 回收子进程
6. 僵尸进程的特点
出现在进程表中,占用一个PID
已释放大部分资源,仅保留退出状态等信息
PPID为创建它的父进程
命令显示为
[进程名] <defunct>
只能通过终止父进程或让父进程调用wait()来清除
7. 僵尸进程的危害
- 僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
- 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
- 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
- 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。
7、死亡状态(X - Dead/EXIT_DEAD)
死亡状态(X)是进程的最终状态,表示进程已经完全终止且其资源已被系统回收。由于这个状态持续时间极短(几乎是瞬时的),直接观察非常困难。
理解死亡状态的特点
瞬时性:X状态是进程从退出到完全消失之间的瞬时状态
不可见性:通常无法通过
ps
等工具直接观察到前驱状态:进程通常从Z(僵尸)状态转为X状态
二、常见问题
僵尸进程积累:
原因:父进程未正确处理子进程退出。
解决:找到父进程ID(PPID)并重启或发送
SIGCHLD
信号。
不可中断进程卡住:
可能原因:硬件故障(如磁盘坏块)、内核Bug。
排查:
dmesg
查看内核日志,检查硬件健康。
高负载下RUNNING进程过多:使用
vmstat 1
或mpstat -P ALL 1
分析CPU竞争。S
和D
状态的区别:S
可被信号中断,D
必须等待事件完成(即使发送kill -9
无效)。为什么进程长时间处于
D
状态?
可能原因:硬件故障(如磁盘损坏)、内核驱动Bug。
解决方案:检查
dmesg
日志,修复硬件或更新驱动。
三、总结
理解进程状态是系统调优和故障排查的基础。
R
/S
/D
是常见状态,Z
需及时处理,T
常用于调试。结合
ps
、top
、/proc
等工具实时监控状态变化。
掌握这些状态及其转换机制,能有效诊断进程挂起、资源泄漏等问题。实际应用中需结合日志和性能工具(如strace
、perf
)深入分析。