实战Linux进程状态观察:R、S、D、T、Z状态详解与实验模拟

前言

在Linux系统中,进程状态是系统管理和性能调优的核心知识。一个进程从诞生到终止,会经历运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)等多种状态。理解这些状态的含义、触发条件及观察方法,是诊断进程挂起、资源泄漏等问题的关键。

目录

一、进程状态观察详解

[1、运行状态(R - Running/TASK_RUNNING)](#1、运行状态(R - Running/TASK_RUNNING))

示例观察方法

[1. 创建测试程序](#1. 创建测试程序)

[2. 运行程序并观察状态](#2. 运行程序并观察状态)

方法一:使用top命令

方法二:使用ps命令

[2、可中断睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)](#2、可中断睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE))

[3、不可中断睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE)](#3、不可中断睡眠(D - Uninterruptible Sleep/TASK_UNINTERRUPTIBLE))

方法一:模拟磁盘I/O导致的D状态

[1. 创建测试脚本](#1. 创建测试脚本)

[2. 准备观察环境](#2. 准备观察环境)

第一步:查看可用设备

第二步:创建挂载点目录

第三步:执行挂载一个慢速存储设备

第四步:验证挂载

第五步:给脚本执行权限:

第六步:运行并观察

[4、停止状态(T - Stopped/TASK_STOPPED)](#4、停止状态(T - Stopped/TASK_STOPPED))

[5、跟踪状态(T - Tracing Stop)](#5、跟踪状态(T - Tracing Stop))

[1. 创建进程并进入跟踪状态](#1. 创建进程并进入跟踪状态)

[2. 使用调试器附加到进程](#2. 使用调试器附加到进程)

[3. 观察进程状态](#3. 观察进程状态)

[4. 状态变化示例](#4. 状态变化示例)

[5. 其他产生T状态的场景(了解,不要求掌握)](#5. 其他产生T状态的场景(了解,不要求掌握))

[6. 如何解除T状态](#6. 如何解除T状态)

[6、僵尸状态(Z - Zombie/EXIT_ZOMBIE)](#6、僵尸状态(Z - Zombie/EXIT_ZOMBIE))

[1. 创建僵尸进程的C程序](#1. 创建僵尸进程的C程序)

[2. 编译并运行程序](#2. 编译并运行程序)

[3. 观察进程状态](#3. 观察进程状态)

[4. 预期观察到的现象](#4. 预期观察到的现象)

[5. 清理僵尸进程(后面会讲解,现在先了解)](#5. 清理僵尸进程(后面会讲解,现在先了解))

[6. 僵尸进程的特点](#6. 僵尸进程的特点)

[7. 僵尸进程的危害](#7. 僵尸进程的危害)

[7、死亡状态(X - Dead/EXIT_DEAD)](#7、死亡状态(X - Dead/EXIT_DEAD))

理解死亡状态的特点

二、常见问题

三、总结


一、进程状态观察详解

1、运行状态(R - Running/TASK_RUNNING)

进程处于运行状态(running)并不等同于正在执行。运行状态意味着该进程要么正在CPU上运行,要么处于运行队列中等待执行。因此,系统中可以同时存在多个R状态的进程。

重点:

  • 所有可调度的运行状态进程都会被放入运行队列

  • 操作系统进行进程切换时,直接从运行队列中选择下一个要执行的进程

  • 单核CPU上同一时刻只有一个进程真正在CPU上运行

  • 其他R状态的进程都在运行队列中等待

  • 多核CPU上可以有多个进程同时处于"正在运行"状态

示例观察方法

1. 创建测试程序

创建一个简单的CPU密集型程序来观察R状态:

cpp 复制代码
// 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;
}

编译:

bash 复制代码
gcc cpu_busy.c -o cpu_busy

2. 运行程序并观察状态

方法一:使用top命令
  1. 在当前终端窗口运行程序:

    bash 复制代码
    ./cpu_busy
  2. 在另一个终端窗口运行top:

bash 复制代码
top

在top界面中,按Shift + P按CPU使用排序、观察STAT列,会显示R状态:

方法二:使用ps命令
bash 复制代码
ps aux | grep cpu_busy

输出示例:

其中R+表示:

  • R:运行状态

  • +:在前台进程组中

2、可中断睡眠(S - Interruptible Sleep/TASK_INTERRUPTIBLE)

一个进程处于浅度睡眠状态(sleeping)表示它正在等待某个事件的完成。处于这种状态的进程可以被随时唤醒或终止(这种状态也被称为可中断睡眠,即interruptible sleep)。

例如运行以下代码:

cpp 复制代码
#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的脚本:

bash 复制代码
#!/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. 准备观察环境

第一步:查看可用设备

bash 复制代码
lsblk  # 查看所有存储设备

输出示例:

根据lsblk 的输出,可以看到我的系统只有一个虚拟磁盘设备(vda),没有检测到其他存储设备。这是典型云服务器或虚拟机的配置。

  1. vda:虚拟磁盘(40GB)

  2. vda1:第一个分区(已挂载到根目录 /

第二步:创建挂载点目录

bash 复制代码
sudo mkdir /mnt/mydisk  # 创建一个新目录作为挂载点

第三步:执行挂载一个慢速存储设备

bash 复制代码
sudo mount /dev/vda1 /mnt/mydisk

第四步:验证挂载

bash 复制代码
df -h  # 查看已挂载的文件系统

bash 复制代码
ls /mnt/mydisk  # 查看挂载的设备内容

第五步:给脚本执行权限:

bash 复制代码
chmod +x disk_io_test.sh

第六步:运行并观察

  1. 在一个终端运行脚本:

    bash 复制代码
    ./disk_io_test.sh
  2. 在另一个终端观察进程状态:

    bash 复制代码
    watch -n 0.5 'ps aux | grep disk_io_test | grep -v grep'

命令刚启动时的状态,可能还未进入深度睡眠(D状态),而如果进入深度睡眠(D状态),会输出示例,必须要有产生足够的 I/O 压力,才会实现(我模拟不了,大概看一下就行):

bash 复制代码
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. 创建进程并进入跟踪状态

首先,我们需要创建一个可以被跟踪的进程:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("子进程PID: %d\n", getpid());
    while(1) {
        // 无限循环保持进程运行
        sleep(1);
    }
    return 0;
}

编译并运行这个程序,记下它的PID:

2. 使用调试器附加到进程

在另一个终端中,使用gdb附加到该进程:

cpp 复制代码
sudo gdb -p <PID>

使用第三个终端观察,此时,被调试的进程会进入T (Tracing Stop)状态:

3. 观察进程状态

在第三个终端中,查看进程状态:

bash 复制代码
ps -o pid,state,cmd -p <PID>

输出可能类似于:

4. 状态变化示例

  • 初始状态:进程正常运行,状态为"S"(睡眠)

  • 附加调试器后:状态变为"T"(被跟踪)

  • 继续执行:在gdb中输入"continue",状态可能变回"S"

  • 断点命中:当遇到断点时,状态再次变为"T"

5. 其他产生T状态的场景(了解,不要求掌握)

  1. 使用strace跟踪系统调用

    bash 复制代码
    strace -p <PID>
  2. 使用ptrace系统调用Ptrace 详解 - tangr206 - 博客园

    bash 复制代码
    // 跟踪进程的示例代码
    ptrace(PTRACE_ATTACH, pid, NULL, NULL);
  3. 接收到SIGSTOP信号

    bash 复制代码
    kill -SIGSTOP <PID>

6. 如何解除T状态

  • 在gdb中:使用detach命令或退出gdb

  • 对于strace:按Ctrl+C终止strace

  • 对于SIGSTOP:发送SIGCONT信号

    bash 复制代码
    kill -SIGCONT <PID>

6、僵尸状态(Z - Zombie/EXIT_ZOMBIE)

1. 创建僵尸进程的C程序

cpp 复制代码
#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. 编译并运行程序

bash 复制代码
gcc -o zombie zombie.c
./zombie

3. 观察进程状态

重新运行,然后在另一个终端窗口中,使用以下命令观察进程状态变化:

bash 复制代码
watch -n 1 'ps -eo pid,ppid,state,cmd | grep -E "PID|zombie"'

4. 预期观察到的现象

  1. 初始阶段:父子进程都处于运行状态(S)

  2. 子进程退出后

    • 子进程状态变为Z(僵尸)

    • 父进程仍在运行

  3. 最终状态

5. 清理僵尸进程(后面会讲解,现在先了解)

要清理僵尸进程,可以:

  1. 终止父进程:

    bash 复制代码
    kill -9 <父进程PID>
  2. 或者修改程序让父进程调用wait():

    bash 复制代码
    // 在父进程代码中添加
    wait(NULL);  // 回收子进程

6. 僵尸进程的特点

  • 出现在进程表中,占用一个PID

  • 已释放大部分资源,仅保留退出状态等信息

  • PPID为创建它的父进程

  • 命令显示为[进程名] <defunct>

  • 只能通过终止父进程或让父进程调用wait()来清除

7. 僵尸进程的危害

  • 僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
  • 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
  • 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
  • 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。

7、死亡状态(X - Dead/EXIT_DEAD)

死亡状态(X)是进程的最终状态,表示进程已经完全终止且其资源已被系统回收。由于这个状态持续时间极短(几乎是瞬时的),直接观察非常困难。

理解死亡状态的特点

  • 瞬时性:X状态是进程从退出到完全消失之间的瞬时状态

  • 不可见性 :通常无法通过ps等工具直接观察到

  • 前驱状态:进程通常从Z(僵尸)状态转为X状态


二、常见问题

  1. 僵尸进程积累

    • 原因:父进程未正确处理子进程退出。

    • 解决:找到父进程ID(PPID)并重启或发送SIGCHLD信号。

  2. 不可中断进程卡住

    • 可能原因:硬件故障(如磁盘坏块)、内核Bug。

    • 排查:dmesg查看内核日志,检查硬件健康。

  3. 高负载下RUNNING进程过多 :使用vmstat 1mpstat -P ALL 1分析CPU竞争。

  4. SD状态的区别: S可被信号中断,D必须等待事件完成(即使发送kill -9无效)。

  5. 为什么进程长时间处于D状态?

  • **可能原因:**硬件故障(如磁盘损坏)、内核驱动Bug。

  • 解决方案: 检查dmesg日志,修复硬件或更新驱动。


三、总结

  • 理解进程状态是系统调优和故障排查的基础。

  • R/S/D是常见状态,Z需及时处理,T常用于调试。

  • 结合pstop/proc等工具实时监控状态变化。

掌握这些状态及其转换机制,能有效诊断进程挂起、资源泄漏等问题。实际应用中需结合日志和性能工具(如straceperf)深入分析。

相关推荐
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_1 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化