CPU性能篇-系统中出现大量不可中断进程和僵尸进程怎么办? Day 05

在上下文切换的文章中,学习并分析了系统 CPU 使用率高的问题,剩下的等待 I/O 的 CPU 使用率(以下简称为 iowait)升高,也是最常见的一个服务器性能问题。今天就来看一个多进程 I/O 的案例,并分析这种情况。

1. 进程状态

当 iowait 升高时,进程很可能因为得不到硬件的响应,而长时间处于不可中断状态。从 ps 或者 top 命令的输出中,可以发现它们都处于 D 状态,也就是不可中断状态(Uninterruptible Sleep)。

1.1 进程状态介绍

top 和 ps 是最常用的查看进程状态的工具。

$ topPID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
28961 root      20   0   43816   3148   4040 R   3.2  0.0   0:00.01 top620 root      20   0   37280  33676    908 D   0.3  0.4   0:00.01 app1 root      20   0  160072   9416   6752 S   0.0  0.1   0:37.64 systemd1896 root      20   0       0      0      0 Z   0.0  0.0   0:00.00 devapp2 root      20   0       0      0      0 S   0.0  0.0   0:00.10 kthreadd4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq7 root      20   0       0      0      0 S   0.0  0.0   0:06.37 ksoftirqd/0
  • R:是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
  • D:是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
  • Z:是 Zombie 的缩写,它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
  • S:是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
  • I:是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
  • T或者t:Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你用 fg 命令,恢复到前台运行)。
  • X:是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。

1.1.1 D 不可中断状态

该状态是为了保证进程数据与硬件状态一致,而且分成两个场景

  • 正常场景:
    • 不可中断状态在很短时间内就会结束。所以,短时的不可中断状态进程,我们一般可以忽略。
  • 异常场景
    • 系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,就得注意下,系统是不是出现了 I/O 等性能问题。

1.1.2 Z 僵尸进程

这是多进程应用很容易碰到的问题。主要也分为两个场景

  • 正常场景
    • 当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
  • 异常场景
    • 如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。换句话说,父亲应该一直对儿子负责,善始善终,如果不作为或者跟不上,都会导致“问题少年”的出现。

通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。

僵尸进程的危害:大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。

2. 案例分析:多进程应用场景

2.1 环境准备

  • 机器配置:4 CPU,8GB 内存,Ubuntu 22.04.5,1台机器。
  • 预先安装 docker、sysstat、dstat 等工具。dstat 是一个新的性能工具,它吸收了 vmstat、iostat、ifstat 等几种工具的优点,可以同时观察系统的 CPU、磁盘 I/O、网络以及内存使用情况。

安装过程:略

2.2 部署测试应用

注意:这里部署完了一定要立刻进行接下来的步骤,不然立刻关闭这个容器,否则时间长了会把系统的负载打满,导致无法操作。

root@yunwei-virtual-machine:~# docker run --privileged --name=app -itd feisky/app:iowait
27b316cda218482c9870ad54908ec4a4f4daf0ec52d8b488e064c1530b617d74
root@yunwei-virtual-machine:~# docker ps -l
CONTAINER ID   IMAGE               COMMAND   CREATED          STATUS         PORTS     NAMES
27b316cda218   feisky/app:iowait   "/app"    20 seconds ago   Up 5 seconds             app

然后通过ps命令查看应用是否部署成功,如下图就表示部署成功了:

从上图,可以发现多个 app 进程已经启动,并且它们的状态分别是 Ss+、D+、S+。其中,S 表示可中断睡眠状态,D 表示不可中断睡眠状态,我们在前面刚学过,那后面的 s 和 + 是什么意思呢?s 表示这个进程是一个会话的领导进程,而 + 表示前台进程组。

2.3 什么是进程组和会话

  • 进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员;
  • 会话是指共享同一个控制终端的一个或多个进程组。

比如,我们通过 SSH 登录服务器,就会打开一个控制终端(TTY),这个控制终端就对应一个会话。而我们在终端中运行的命令以及它们的子进程,就构成了一个个的进程组,其中,在后台运行的命令,构成后台进程组;在前台运行的命令,构成前台进程组。

2.4 查看系统资源使用情况

这里汇总一下,上图的问题:

  • 第一行 load average: 4.59, 2.10, 1.35。
    • 光是1分钟的负载,就已经打满了(机器4C),这个时候系统的性能很明显已经受到的影响。而且随着时间推移,负载还在不断上升。
  • 第二行 有1个正在运行的进程,但是有12个僵尸进程,这个僵尸进程也是随着时间的推移在不断增多。
  • 第三四五六行 用户态和内核态的cpu使用率都很低,但是iowait有3个CPU>95%,一个73.8,这很明显不正常。
  • 进程状态:CPU使用率最高的进程也只占用了1%,但有好几个进程处于 D 状态,它们可能在等待 I/O,但光凭这里并不能确定是它们导致了 iowait 升高。

那么现状总结下来就是:

  1. iowait 太高了,导致系统的平均负载升高,甚至超过了系统 CPU 的个数。
  2. 僵尸进程在不断增多,说明有程序没能正确清理子进程的资源。

2.5 iowait分析

推荐使用dstat ,它的好处是可以同时查看 CPU 和 I/O 这两种资源的使用情况,便于对比分析。

root@yunwei-virtual-machine:~# dstat 1 10 # 间隔1秒输出10组数据
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read  writ| recv  send|  in   out | int   csw0   1  85  13   0|  57M   18k|   0     0 |   0     0 | 320   3670   3   0  97   0| 411M    0 | 606B  338B|   0     0 |1172  16420   2  10  88   0| 499M    0 | 846B  248B|   0     0 |1212  19030   2   0  98   0| 319M 4096B| 858B  330B|   0     0 | 913  13351   3   0  96   0| 372M 4096B| 786B  162B|   0     0 |1109  14631   7   0  92   0| 472M   32k| 546B  162B|   0     0 |1469  18190   3   0  97   0| 435M    0 | 546B  162B|   0     0 |1218  17380   4   4  92   0| 568M    0 | 666B  146B|   0     0 |1481  21200   2   0  97   0| 348M    0 | 906B  383B|   0     0 |1021  14000   4   6  89   0| 471M    0 | 846B  146B|   0     0 |1339  1843

从 dstat 的输出,我们可以看到,每当 iowait 升高(wa)时,磁盘的读请求(read)都会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。

2.6 查找导致iowait升高的进程

继续在刚才的终端中,运行 top 命令,观察 D 状态的进程:

观察一会儿按 Ctrl+C 结束

然后随便找一个D状态进程的PID,使用pidstat -d来分析io使用情况:

root@yunwei-virtual-machine:~# pidstat -d -p 22540 1 3 # 每隔一秒输出3组数据
Linux 6.8.0-60-generic (yunwei-virtual-machine)         2025年06月18日  _x86_64_        (4 CPU)16时51分57秒   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
16时51分58秒     0     22540  65536.00      0.00      0.00       0  app
16时51分59秒     0     22540      0.00      0.00      0.00       0  app
16时52分00秒     0     22540  65536.00      0.00      0.00       0  app
Average:        0     22540  43690.67      0.00      0.00       0  app

通过结果可以发现,就是这个app进程在读取磁盘,并且每秒最多有64m的读取。

2.7 查找进程到底在执行什么样的IO操作

回顾一下进程用户态和内核态的区别:

  • 进程想要访问磁盘,就必须使用系统调用,所以接下来,重点就是找出 app 进程的系统调用了。

strace 正是最常用的跟踪进程系统调用的工具。所以,我们从 pidstat 的输出中拿到进程的 PID 号,比如 6082,然后在终端中运行 strace 命令,并用 -p 参数指定 PID 号:

$ strace -p 6082
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted

这里居然报错了,显示“不允许操作!”。

$ ps aux | grep 6082
root      6082  0.0  0.0      0     0 pts/0    Z+   13:43   0:00 [app] <defunct>

然后通过ps查看进程,发现进程已经变成了僵尸进程(Z),僵尸进程是已经退出运行的进程,是没有办法分析它的系统调用的。

但此时系统的iowait依然还是处于一个异常状态,而使用top、pidstat这类的工具,也无法查出更多有用的信息,怎么办?使用基于事件记录的动态追踪工具。

2.7.1 使用perf查看调用栈信息

root@yunwei-virtual-machine:/tmp# perf record -g # 生成调用用栈文件,约15s后CTRL C退出。
root@yunwei-virtual-machine:/tmp# ll -rth
-rw-------  1 root   root    61M  6月 26 16:33 perf.dataroot@yunwei-virtual-machine:/tmp# perf report # 查看调用栈

接着,找到我们关注的 app 进程,按回车键展开调用栈,你就会得到下面这张调用关系图:

这个图里的 swapper 是内核中的调度进程,可以先忽略掉。

可以发现, app 的确在通过系统调用 sys_read() 读取数据。并且从 new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高了。

看来,罪魁祸首是 app 内部进行了磁盘的直接 I/O 啊!

下面的问题就容易解决了。我们接下来应该从代码层面分析,究竟是哪里出现了直接读请求。查看源码文件 app.c,你会发现它果然使用了 O_DIRECT 选项打开磁盘,于是绕过了系统缓存,直接对磁盘进行读写。

open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)

直接读写磁盘,对 I/O 敏感型应用(比如数据库系统)是很友好的,因为你可以在应用中,直接控制磁盘的读写。但在大部分情况下,我们最好还是通过系统缓存来优化磁盘 I/O,换句话说,删除 O_DIRECT 这个选项就是了。

3. 僵尸进程

僵尸进程是因为父进程没有回收子进程的资源而出现的,那么,要解决掉它们,就要找到它们的根儿,也就是找出父进程,然后在父进程里解决。

3.1 使用pstree查看进程关系

# -a 表示输出命令行选项
# p表PID
# s表示指定进程的父进程
$ pstree -aps 3084
systemd,1└─dockerd,15006 -H fd://└─docker-containe,15024 --config /var/run/docker/containerd/containerd.toml└─docker-containe,3991 -namespace moby -workdir...└─app,4009└─(app,3084)

运行完,你会发现 3084 号进程的父进程是 4009,也就是 app 应用。这种怎么办呢?查看 app 应用程序的代码,看看子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,抑或是,有没有注册 SIGCHLD 信号的处理函数。找到并修复就好了。

4. 小结

iowait 高不一定代表 I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。

因此,碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。

等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到的 D 状态(即不可中断状态)的进程,多为可疑进程。但这个案例中,在 I/O 操作后,进程又变成了僵尸进程,所以不能用 strace 直接分析这个进程的系统调用。

这种情况下,我们用了 perf 工具,来分析系统的 CPU 时钟事件,最终发现是直接 I/O 导致的问题。这时,再检查源码中对应位置的问题,就很轻松了。

而僵尸进程的问题相对容易排查,使用 pstree 找出父进程后,去查看父进程的代码,检查 wait() / waitpid() 的调用,或是 SIGCHLD 信号处理函数的注册就行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86311.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core + Jenkins 实现自动化发布

一、安装Jenkins 我这边服务器是Linux CentOS 7 &#xff0c;使用SSH 登录云服务器后&#xff0c;输入以下命令安装jenkins. sudo wget -O /etc/yum.repos.d/jenkins.repo \https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/red…

Java项目RestfulAPI设计最佳实践

大家好&#xff0c;我是锋哥。今天分享关于【Java项目RestfulAPI设计最佳实践】面试题。希望对大家有帮助&#xff1b; Java项目RestfulAPI设计最佳实践 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; 设计一个高效、易维护的 Java 项目中的 RESTful API 涉及到一…

FANUC机器人教程:用户坐标系标定及其使用方法

目录 概述 工作站创建 任务描述 用户坐标系标定方法 用户坐标系标定操作 用户坐标系手动测试 用户坐标系在程序中的应用 用户坐标系选择指令介绍 机器人示教编程 仿真运行 仿真案例资源下载 概述 FANUC机器人的用户坐标系&#xff0c;是用户对每个作业空间定义的直…

动态库与静态库【Linux】

程序编译过程 源代码(.cpp) → 预处理(.i) → 编译(.s) → 汇编(.o) → 链接(可执行文件) g -o main.i -E main.cpp 参数说明&#xff1a; 参数功能输出文件类型-E仅预处理.i-S预处理 编译.s-c预处理 编译 汇编.o无完整流程&#xff08;预处理→编译→汇编→链接&…

MySQL MHA 故障转移-VIP

MHA故障转移-VIP #手工在主库添加VIP ifconfig ens33:1 192.168.80.200/24配置VIP脚本 vim /usr/local/bin/master_ip_failoverchmod x /usr/local/bin/#!/usr/bin/env perl use strict; use warnings FATAL > all;use Getopt::Long;my ( $command, $ssh_user, $orig_mast…

Elasticsearch索引字段的类型

在 Elasticsearch 中&#xff0c;索引字段的类型&#xff08;即 Mapping 中的字段类型&#xff09;对搜索和存储性能影响很大。下面是各种常用数据类型的用途及推荐使用场景总结&#xff1a; 1. keyword 类型&#xff08;精确匹配&#xff09; 适合数据&#xff1a; 不需要分词…

kubernetes证书续签-使用kubeadm更新证书(下)

#作者&#xff1a;任少近 文章目录 查看kubelet证书查看kubelet当前所使用的证书 更换 node上的kubelet证书生成node1所需要的kubelet.conf文件生成node2所需要的kubelet.conf文件查看csr 更新 ~/.kube/config 文件重启相关组件 查看kubelet证书 以上少了kubelet的证书&#…

AI智能体长期记忆系统架构设计:从认知模型到生产实践

1 长期记忆:AI智能体的认知基石 1.1 人类记忆与AI记忆的类比 #mermaid-svg-VIPKAFe7VgN4UHFA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VIPKAFe7VgN4UHFA .error-icon{fill:#552222;}#mermaid-svg-VIPKAFe7V…

快速上手:利用音频大模型与Java提取视频文案

文章目录 1、前言2、需求说明2.1 需求说明2.2 数据准备 3、功能实现3.1 使用视频理解大模型能力3.1.1 三方平台视频在线链接解析3.1.2 三方平台视频内网链接解析3.1.3 三方平台视频转存本地服务 3.2 使用音频识别大模型能力3.2.1 三方平台视频在线链接解析3.2.2 三方平台视频详…

LLM复杂记忆存储-多会话隔离案例实战

导读&#xff1a;在多用户并发的对话系统中&#xff0c;会话隔离问题往往成为开发者面临的技术难题。当数千个用户同时与AI助手交互时&#xff0c;如何确保每个用户的对话历史完全独立&#xff0c;避免数据混淆和隐私泄露&#xff1f; 本文深入剖析了基于RunnableWithMessageHi…

【PX4-AutoPilot教程-TIPS】PX4系统命令行控制台ConsolesShells常用命令(持续更新)

PX4系统命令行控制台 Consoles & Shells 常用命令 查看每个应用程序的堆栈使用情况获取所有可用命令和APP的列表应用程序启动、停止和状态查询查看本地文件系统查看剩余的可用RAM查看工作队列中正在运行的内容以及运行速率查看特定的uORB话题调试uORB话题进行模式切换和故障…

国内优秀wordpress主题推荐

在国内&#xff0c;WordPress 主题市场虽然不如国外那样庞大&#xff0c;但依然有许多优秀且适合中国用户需求的主题。以下是一些经过评估和推荐的国内优秀WordPress主题&#xff0c;涵盖不同类型的网站需求&#xff0c;如博客、企业官网、资源站、社区论坛等。 WP汉主题 WP汉…

第 6 章:进阶话题

第 6 章&#xff1a;进阶话题 过拟合vs欠拟合&#xff1a;模型复杂度和泛化能力的关系 在前面的章节中&#xff0c;我们已经学习了神经网络的基础知识、常见架构和基本训练流程。然而&#xff0c;在实际的深度学习项目中&#xff0c;仅仅掌握这些基础知识是不够的。我们还需要…

4.2_1朴素模式匹配算法

知识总览&#xff1a; 什么是字符串的模式匹配&#xff1a; 主串&#xff1a;想从该串获取结果的串 模式串&#xff1a;想搜索的内容&#xff0c;不一定在主串中能搜到&#xff0c;子串一定能在主串中搜到 字符串模式匹配&#xff1a;在主串找模式串并返回找到的第一个模式串…

华为云Flexus+DeepSeek征文|华为云ModelArts搭建Dify-LLM应用开发平台(AI智能选股大模型)

前言 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术在金融领域的应用愈发广泛&#xff0c;其中 AI 智能选股大模型备受关注。为了构建高效且精准的 AI 智能选股大模型&#xff0c;选择合适的开发平台和工具至关重要。华为云 ModelArts 作为一款面向 AI …

C4.5算法深度解析:决策树进化的里程碑

C4.5是机器学习史上最经典的算法之一&#xff0c;由ID3之父Ross Quinlan在1993年提出。作为ID3的革命性升级&#xff0c;它不仅解决了前代的核心缺陷&#xff0c;更开创了连续特征处理和剪枝技术的先河&#xff0c;成为现代决策树的奠基之作。 本文由「大千AI助手」原创发布&am…

leetcode 65

#include <string> #include <vector> #include <unordered_map> using namespace std;class Solution { public:bool isNumber(string s) {// 定义状态转移表vector<unordered_map<char, int>> states {{{ , 0}, {s, 1}, {d, 2}, {., 4}}, // …

微服务(nacos+myibatis)中如何在一个模块调用多数据库源的一种方案

#nacos配置默认数据库 spring.datasource.typecom.alibaba.druid.pool.DruidDataSource spring.datasource.driverNamecom.mysql.jdbc.Driver #默认数据库名 master spring.datasource.dynamic.primarymaster spring.datasource.dynamic.strictfalse spring.datasource.d…

高标准通信国际接轨,Ethercat与PROFINET网关实现全自动化生产线

在呼和浩特&#xff0c;集成商以其先进的食品饮料行业解决方案&#xff0c;为乳制品行业打造了一个智能化工厂的典范。这个工厂的核心是PROFINET全集成自动化&#xff08;TIA&#xff09;&#xff0c;它通过SIMATIC S7-1200 PLC和ethercat系统&#xff0c;构建了一个强大的PROF…

Netty 引用计数抽象类 AbstractReferenceCountedByteBuf 详解

核心类图 ----------------------------- ---------------------------------- | ReferenceCountUpdater | | AbstractReferenceCountedByteBuf | | <T extends ReferenceCounted>| | (extends AbstractByteBuf) | ----------…