Linux CPU性能优化:D状态和Z状态排查与处理
文章目录
- 一、Linux进程五大基本状态
- 1. 运行状态(R,Running / Runnable)
- 2. 可中断睡眠状态(S,Interruptible Sleep)
- 3. 不可中断睡眠状态(D,Uninterruptible Sleep)
- 4. 停止状态(T,Stopped / Traced)
- 5. 僵死状态(Z,Zombie / Defunct)
- 二、D 状态和Z状态排查
- 1.D 状态排查方法
- 1. wchan & stack
- 2. hung_task
- 2. Z 状态排查方法
- 3.防止Z状态产生
一、Linux进程五大基本状态
在 Linux 系统中,常用的五大基本状态(通过 ps、top 等命令可查看)分别是:
1. 运行状态(R,Running / Runnable)
含义:
进程当前正在运行(占用 CPU)或者处于可运行队列中、只要获得 CPU 时间片就能立刻执行。
特点:
包括正在 CPU 上执行的进程,以及就绪等待调度的进程。
这是进程争取 CPU 时的活跃状态。
示例:
一个不停计算的 while(1) 程序通常处于 R 状态;在多核系统上,多个 R 状态进程可能同时运行。
2. 可中断睡眠状态(S,Interruptible Sleep)
含义:
进程正在等待某个事件完成(如等待 I/O 输入、等待锁、等待信号等),并且该睡眠可以被信号唤醒。
特点:
最常见的睡眠状态(大部分时间系统进程都在此状态)。
当等待的资源可用或收到信号时,进程会回到 R 状态。
可以被 kill 命令或其它信号打断。
示例:
Shell 等待用户输入、网络服务等待客户端连接、进程调用 sleep() 或 nanosleep()。
3. 不可中断睡眠状态(D,Uninterruptible Sleep)
含义:
进程正在等待某些不可被中断的 I/O 操作(如直接读写磁盘、等待硬件响应),期间无法响应任何信号。
特点:
通常用于磁盘 I/O、某些设备驱动中的关键操作。
即使发送 SIGKILL 也无法立即终止该进程(必须等待 I/O 完成或系统重启)。
这种设计是为了防止在关键数据写回磁盘时被中断导致数据不一致。
示例:
sync 命令刷新缓冲区、dd 直接写入块设备、NFS 因网络故障僵住时的等待操作。
注意:短时间的 D 状态是正常的,若长时间存在且数量增多,往往提示 I/O 瓶颈或存储设备问题。
4. 停止状态(T,Stopped / Traced)
含义:
进程的执行被暂停,通常是由于收到了 SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 等信号,或者被调试器(如 gdb)暂时接管。
特点:
进程不会获得 CPU 时间,也不响应普通信号(除 SIGKILL、SIGCONT 外)。
可以通过发送 SIGCONT 信号让进程恢复到 R 状态继续执行。
示例:
在终端按下 Ctrl+Z 将前台进程挂起 或 gdb 中设置断点后命中暂停 或 使用 kill -SIGSTOP 显式暂停。
5. 僵死状态(Z,Zombie / Defunct)
含义:
进程已经结束运行(调用了 exit 或收到终止信号),但其进程描述符(task_struct)仍然保留,等待父进程调用 wait() 或 waitpid() 来读取其退出状态。
特点:
该进程不再占用任何内存或 CPU 资源(只保留内核中的一个极小结构体)。
无法被杀死(因为它已经“死”了),只能通过让父进程回收它来消除。
若父进程不回收且不退出,僵尸进程会一直存在;若父进程先退出,僵尸进程会被 init(PID=1)进程收养并自动回收。
示例:
父进程编写不当(未调用 wait),导致子进程结束后一直处于 Z 状态。大量僵尸进程可能耗尽进程号上限,影响系统运行。
本文将具体介绍D状态和Z状态的排查与处理。
二、D 状态和Z状态排查
1.D 状态排查方法
1. wchan & stack
wchan(wait channel)能看到 D 状态进程“卡”在内核的哪个具体函数
例如:
ps-eopid,state,wchan,comm|awk'$2=="D"'输出
PID S WCHAN COMMAND123D io_scheduledd456D wait_on_page_bit mysqld这个例子中进程dd(PID 123) 卡在了 io_schedule, 即磁盘读写,可能的原因是磁盘慢、坏道、NFS 挂了。
而进程mysqld(PID 456)卡在了wait_on_page_bit,即内存页回写,可能的原因是内存压力大。
也可以直接读取某进程的wchan,例如:
cat/proc/798/wchan可能输出为:
rpc_wait_bit_killable即卡在RPC 层。
然后,可以通过stack中内容进一步排查具体卡在了哪个函数,例如在刚才的例子中继续检查stack:
cat/proc/789/stack可能输出为:
[<0>]rpc_wait_bit_killable+0x??/0x??[sunrpc][<0>]__rpc_execute+0x??/0x??[sunrpc][<0>]rpc_run_task+0x??/0x??[sunrpc][<0>]nfs_write_rpc+0x??/0x??[nfs][<0>]nfs_file_write+0x??/0x??[nfs][<0>]vfs_write+0x??/0x??[<0>]sys_write+0x??/0x??由此能看出是 NFS 写操作卡在 RPC 层。
2. hung_task
当 D 状态进程超过 120 秒(默认),内核会主动打印带有hung task 关键字的log,因此使用dmesg 指令可以可以获取
dmesg-T|grep"hung_task"可能输出为:
[Thu May2510:15:322026]INFO: task dd:123 blockedformorethan120seconds.[Thu May2510:15:322026]Tainted: G W[Thu May2510:15:322026]"echo 0 > /proc/sys/kernel/hung_task_timeout_secs"disables this message.[Thu May2510:15:322026]ddD12310x00000000[Thu May2510:15:322026]Call Trace:[Thu May2510:15:322026][<ffffffffa0000000>]io_schedule+0x??/0x??可以看出 dd(PID 123)卡在 io_schedule 超过 120 秒。
2. Z 状态排查方法
wchan、hung_task 等指令对Z状态无用,我们需要一直找到进程的父进程进行处理。
例如我们已经找到了一个Z状态进程:
psaux|awk'$8=="Z"'输出:
USERPID STAT COMMAND root123Z[sh]<defunct>我们需要找到它的父进程:
ps-oppid=-p123例如输出456,我们通知父进程回收:
kill-CHLD456以此来回收Z状态子进程123.
如果不行,可以选择直接杀死父进程:
kill-94563.防止Z状态产生
最好防止Z状态产生的方式实在代码中确保父进程回收子进程的退出状态。
例如一个非阻塞式回收的代码示例如下:
#include<sys/wait.h>#include<signal.h>#include<unistd.h>#include<stdio.h>#include<errno.h>voidsigchld_handler(intsigno){intsaved_errno=errno;while(waitpid(-1,NULL,WNOHANG)>0);// 回收所有已结束的子进程errno=saved_errno;}intmain(){structsigactionsa;sa.sa_handler=sigchld_handler;sigemptyset(&sa.sa_mask);sa.sa_flags=SA_RESTART|SA_NOCLDSTOP;sigaction(SIGCHLD,&sa,NULL);pid_t pid=fork();if(pid==0){printf("Child exiting\n");return0;}elseif(pid>0){printf("Parent doing other work...\n");sleep(2);// 模拟父进程的工作}return0;}或者使用通过第二次 fork,使实际工作的子进程成为孤儿,被 init(PID=1)接管。init 会自动回收子进程,从而避免僵尸。
#include<sys/wait.h>#include<unistd.h>#include<stdio.h>intmain(){pid_t pid=fork();if(pid==0){// 第一次子进程pid_t pid2=fork();if(pid2==0){// 第二次子进程:实际工作的进程printf("Working child (will be adopted by init)\n");sleep(2);return0;}elseif(pid2>0){// 第一次子进程直接退出,使第二次子进程成为孤儿return0;}}elseif(pid>0){// 父进程回收第一次子进程(很快完成)wait(NULL);printf("Parent: first child reaped, grandchild adopted by init\n");sleep(3);}return0;}