嘉兴住房和城乡建设厅网站,uc做购物网站,代码给wordpress添加图片,网站建设教程数据库进程状态
当iowait升高时#xff0c;进程很可能因为得不到硬件的响应#xff0c;而长时间处于不可中断的状态#xff0c;从ps或者top命令的输出中#xff0c;可以发现它们都处于D状态#xff0c;也就是不可中断状态。
通过top和ps可以查看进程的状态#xff0c;S列表示…进程状态
当iowait升高时进程很可能因为得不到硬件的响应而长时间处于不可中断的状态从ps或者top命令的输出中可以发现它们都处于D状态也就是不可中断状态。
通过top和ps可以查看进程的状态S列表示进程的状态。
$ 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 )不可中断状态睡眠一般表示进程正在跟硬件交互并且交互过程不允许被其他进程或中断打断
Z(Zombie僵尸进程进程实际上已经结束了但是父进程还没有回收它的资源
S(Interruptible Sleep)可中断状态睡眠表示进程因为等待某个时间而被系统挂起当进程等待的时间发生时会被唤醒并进入R状态
I(Idle)空闲的状态用在不可中断睡眠的内核线程上硬件交互导致的不可中断进程用D表示但对某些内核线程来说有可能实际上并没有任何负载用Idle正式为了区分这种情况D状态的进程会导致平均负载升高I状态的进程却不会。
T(Stopped或Traced):表示进程处于暂停或者跟踪状态
向一个进程发送 SIGSTOP 信号它就会因响应这个信号变成暂停状态Stopped再向它发送 SIGCONT 信号进程又会恢复运行如果进程是终端里直接启动的则需要你用 fg 命令恢复到前台运行。用调试器如 gdb调试一个进程时在使用断点中断进程后进程就会变成跟踪状态这其实也是一种特殊的暂停状态只不过你可以用调试器来跟踪并按需要控制进程的运行。
XDead进程已经消亡不会在 top 或者 ps 命令中看到它。
不可中断状态为了保证进程数据与硬件状态一致
正常情况下不可中断状态在很短时间内就会结束短时的不可中断状态进程一般可以忽略。如果系统或硬件发生了故障进程可能会在不可中断状态保持很久甚至导致系统中出现大量不可中断进程。这时就得看下系统是不是出现了 I/O 等性能问题。
僵尸进程这是多进程应用很容易碰到的问题。
正常情况下一个进程创建子进程后通过系统调用 wait() 或者 waitpid() 等待子进程结束回收子进程的资源子进程在结束时会向它的父进程发送 SIGCHLD 信号所以父进程还可以注册 SIGCHLD 信号的处理函数异步回收资源。如果父进程没这么做或是子进程执行太快父进程还没来得及处理子进程状态子进程就已经提前退出那这时的子进程就会变成僵尸进程。通常僵尸进程持续的时间都比较短在父进程回收它的资源后就会消亡或者在父进程退出后由 init 进程回收后也会消亡。一旦父进程没有处理子进程的终止还一直保持运行状态那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号导致新进程不能创建所以这种情况一定要避免。
案例分析
一个多进程应用的案例分析大量不可中断状态和僵尸状态进程的问题。
dstat 是一个新的性能工具它吸收了 vmstat、iostat、ifstat 等几种工具的优点可以同时观察系统的 CPU、磁盘 I/O、网络以及内存使用情况。
首先执行下面的命令运行案例应用
$ docker run --privileged --nameapp -itd feisky/app:iowait
输入 ps 命令确认案例应用已正常启动。如果一切正常你应该可以看到如下所示的输出
$ ps aux | grep /app
root 4009 0.0 0.0 4376 1008 pts/0 Ss 05:51 0:00 /app
root 4287 0.6 0.4 37280 33660 pts/0 D 05:54 0:00 /app
root 4288 0.6 0.4 37280 33668 pts/0 D 05:54 0:00 /app
从这个界面我们可以发现多个 app 进程已经启动并且它们的状态分别是 Ss 和 D。
S 表示可中断睡眠状态D 表示不可中断睡眠状态s 表示这个进程是一个会话的领导进程而 表示前台进程组。
进程组和会话它们用来管理一组相互关联的进程
进程组表示一组相互关联的进程比如每个子进程都是父进程所在组的成员会话是指共享同一个控制终端的一个或多个进程组。
比如我们通过 SSH 登录服务器就会打开一个控制终端TTY这个控制终端就对应一个会话。
而我们在终端中运行的命令以及它们的子进程就构成了一个个的进程组其中在后台运行的命令构成后台进程组在前台运行的命令构成前台进程组。
用 top 看一下系统的资源使用情况
# 按下数字 1 切换到所有 CPU 的使用情况观察一会儿按 CtrlC 结束
$ top
top - 05:56:23 up 17 days, 16:45, 2 users, load average: 2.00, 1.68, 1.39
Tasks: 247 total, 1 running, 79 sleeping, 0 stopped, 115 zombie
%Cpu0 : 0.0 us, 0.7 sy, 0.0 ni, 38.9 id, 60.5 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.7 sy, 0.0 ni, 4.7 id, 94.6 wa, 0.0 hi, 0.0 si, 0.0 st
...PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app1 root 20 0 160072 9416 6752 S 0.0 0.1 0:38.59 systemd
...
平均负载 Load Average过去 1 分钟、5 分钟和 15 分钟内的平均负载在依次减小说明平均负载正在升高而 1 分钟内的平均负载已经达到系统的 CPU 个数说明系统很可能已经有了性能瓶颈。Tasks有 1 个正在运行的进程但僵尸进程比较多而且还在不停增加说明有子进程在退出时没被清理。两个 CPU 的使用率情况用户 CPU 和系统 CPU 都不高但 iowait 分别是 60.5% 和 94.6%好像有点儿不正常。每个进程的情况 CPU 使用率最高的进程只有 0.3%看起来并不高但有两个进程处于 D 状态它们可能在等待 I/O但光凭这里并不能确定是它们导致了 iowait 升高。
四个问题再汇总一下就可以得到很明确的两点
第一点iowait 太高了导致系统的平均负载升高甚至达到了系统 CPU 的个数。第二点僵尸进程在不断增多说明有程序没能正确清理子进程的资源。
相关视频推荐
初识Linux内核进程通信能这么玩
360度无死角讲解进程管理调度器的5种实现
90分钟搞定协程、线程、进程大厂面试
学习地址c/c linux服务器开发/后台架构师
需要C/C Linux服务器架构师学习资料加qun812855908获取资料包括C/CLinuxgolang技术NginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDKffmpeg等免费分享 iowait 分析
iowait 升高首先会想要查询系统的 I/O 情况使用dstat 可以同时查看 CPU 和 I/O 这两种资源的使用情况便于对比分析。
# 间隔 1 秒输出 10 组数据
$ dstat 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 0 96 4 0|1219k 408k| 0 0 | 0 0 | 42 8850 0 2 98 0| 34M 0 | 198B 790B| 0 0 | 42 1380 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 42 1350 0 84 16 0|5633k 0 | 66B 342B| 0 0 | 52 1770 3 39 58 0| 22M 0 | 66B 342B| 0 0 | 43 1440 0 0 100 0| 34M 0 | 200B 450B| 0 0 | 46 1470 0 2 98 0| 34M 0 | 66B 342B| 0 0 | 45 1340 0 0 100 0| 34M 0 | 66B 342B| 0 0 | 39 1310 0 83 17 0|5633k 0 | 66B 342B| 0 0 | 46 1680 3 39 59 0| 22M 0 | 66B 342B| 0 0 | 37 134
从 dstat 的输出可以看到每当 iowait 升高wai时磁盘的读请求read都会很大。
这说明 iowait 的升高跟磁盘的读请求有关很可能就是磁盘读导致的。
运行 top 命令观察 D 状态的进程
# 观察一会儿按 CtrlC 结束
$ top
...PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND4340 root 20 0 44676 4048 3432 R 0.3 0.0 0:00.05 top4345 root 20 0 37280 33624 860 D 0.3 0.0 0:00.01 app4344 root 20 0 37280 33624 860 D 0.3 0.4 0:00.01 app
...
从 top 的输出找到 D 状态进程的 PID你可以发现这个界面里有两个 D 状态的进程PID 分别是 4344 和 4345。
接着我们查看这些进程的磁盘读写情况。查看某一个进程的资源使用情况都可以用我们的 pidstat不过这次记得加上 -d 参数以便输出 I/O 使用情况。
以 4344 为例运行下面的 pidstat 命令并用 -p 4344 参数指定进程号
# -d 展示 I/O 统计数据-p 指定进程号间隔 1 秒输出 3 组数据
$ pidstat -d -p 4344 1 3
06:38:50 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:38:51 0 4344 0.00 0.00 0.00 0 app
06:38:52 0 4344 0.00 0.00 0.00 0 app
06:38:53 0 4344 0.00 0.00 0.00 0 app
kB_rd 表示每秒读的 KB 数kB_wr 表示每秒写的 KB 数iodelay 表示 I/O 的延迟单位是时钟周期
它们都是 0那就表示此时没有任何的读写说明问题不是 4344 进程导致的。
可是用同样的方法分析进程 4345你会发现它也没有任何磁盘读写。
继续使用 pidstat但这次去掉进程号干脆就来观察所有进程的 I/O 使用情况。
在终端中运行下面的 pidstat 命令
# 间隔 1 秒输出多组数据 (这里是 20 组)
$ pidstat -d 1 20
...
06:48:46 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:47 0 4615 0.00 0.00 0.00 1 kworker/u4:1
06:48:47 0 6080 32768.00 0.00 0.00 170 app
06:48:47 0 6081 32768.00 0.00 0.00 184 app06:48:47 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:48 0 6080 0.00 0.00 0.00 110 app06:48:48 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:49 0 6081 0.00 0.00 0.00 191 app06:48:49 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command06:48:50 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:51 0 6082 32768.00 0.00 0.00 0 app
06:48:51 0 6083 32768.00 0.00 0.00 0 app06:48:51 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:52 0 6082 32768.00 0.00 0.00 184 app
06:48:52 0 6083 32768.00 0.00 0.00 175 app06:48:52 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
06:48:53 0 6083 0.00 0.00 0.00 105 app
...
观察一会儿可以发现的确是 app 进程在进行磁盘读并且每秒读的数据有 32 MB看来就是 app 的问题。
不过app 进程到底在执行啥 I/O 操作呢
进程想要访问磁盘就必须使用系统调用所以接下来重点就是找出 app 进程的系统调用了。
strace 正是最常用的跟踪进程系统调用的工具从 pidstat 的输出中拿到进程的 PID 号比如 6082然后在终端中运行 strace 命令并用 -p 参数指定 PID 号
$ strace -p 6082
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted
这儿出现了一个奇怪的错误strace 命令居然失败了并且命令报出的错误是没有权限。
一般遇到这种问题时我会先检查一下进程的状态是否正常。比如继续在终端中运行 ps 命令并使用 grep 找出刚才的 6082 号进程
$ ps aux | grep 6082
root 6082 0.0 0.0 0 0 pts/0 Z 13:43 0:00 [app] defunct
进程 6082 已经变成了 Z 状态也就是僵尸进程。僵尸进程都是已经退出的进程所以就没法儿继续分析它的系统调用。
到这一步你应该注意到了系统 iowait 的问题还在继续但是 top、pidstat 这类工具已经不能给出更多的信息了。
基于事件记录的动态追踪工具用 perf top 看看有没有新发现。或者在终端中运行 perf record持续一会儿例如 15 秒然后按 CtrlC 退出再运行 perf report 查看报告
$ perf record -g
$ perf report
找到我们关注的 app 进程按回车键展开调用栈你就会得到下面这张调用关系图 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 这个选项就是了。
app-fix1.c 就是修改后的文件我也打包成了一个镜像文件运行下面的命令你就可以启动它了
# 首先删除原来的应用
$ docker rm -f app
# 运行新的应用
$ docker run --privileged --nameapp -itd feisky/app:iowait-fix1
最后再用 top 检查一下
$ top
top - 14:59:32 up 19 min, 1 user, load average: 0.15, 0.07, 0.05
Tasks: 137 total, 1 running, 72 sleeping, 0 stopped, 12 zombie
%Cpu0 : 0.0 us, 1.7 sy, 0.0 ni, 98.0 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 1.3 sy, 0.0 ni, 98.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
...PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND3084 root 20 0 0 0 0 Z 1.3 0.0 0:00.04 app3085 root 20 0 0 0 0 Z 1.3 0.0 0:00.04 app1 root 20 0 159848 9120 6724 S 0.0 0.1 0:09.03 systemd2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd3 root 20 0 0 0 0 I 0.0 0.0 0:00.40 kworker/0:0
...
iowait 已经非常低了只有 0.3%说明刚才的改动已经成功修复了 iowait 高的问题大功告成不过别忘了僵尸进程还在等着你。仔细观察僵尸进程的数量你会郁闷地发现僵尸进程还在不断的增长中。
僵尸进程
接下来我们就来处理僵尸进程的问题。既然僵尸进程是因为父进程没有回收子进程的资源而出现的那么要解决掉它们就要找到它们的根儿也就是找出父进程然后在父进程里解决。
父进程的找法我们前面讲过最简单的就是运行 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 信号的处理函数。
现在我们查看修复 iowait 后的源码文件 app-fix1.c 找到子进程的创建和清理的地方
nt status 0;for (;;) {for (int i 0; i 2; i) {if(fork() 0) {sub_process();}}sleep(5);}while(wait(status)0);
循环语句本来就容易出错你能找到这里的问题吗这段代码虽然看起来调用了 wait() 函数等待子进程结束但却错误地把 wait() 放到了 for 死循环的外面也就是说wait() 函数实际上并没被调用到我们把它挪到 for 循环的里面就可以了。
修改后的文件我放到了 app-fix2.c 中也打包成了一个 Docker 镜像运行下面的命令你就可以启动它
# 先停止产生僵尸进程的 app
$ docker rm -f app
# 然后启动新的 app
$ docker run --privileged --nameapp -itd feisky/app:iowait-fix2
启动后再用 top 最后来检查一遍
$ top
top - 15:00:44 up 20 min, 1 user, load average: 0.05, 0.05, 0.04
Tasks: 125 total, 1 running, 72 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 1.7 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 1.3 sy, 0.0 ni, 98.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
...PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND3198 root 20 0 4376 840 780 S 0.3 0.0 0:00.01 app2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd3 root 20 0 0 0 0 I 0.0 0.0 0:00.41 kworker/0:0
...
总结
通过ps或者top可以查看进程的状态这些进程的状态包括运行 ( R )、空闲(I)、不可中断睡眠(D)、可中断睡眠状态(S)、僵尸(Z)、以及暂停(T)。
不可中断状态表示正在跟硬件交互为了保持一致性系统不允许其他进程或中断打断这个进程进程长时间处于不可中断状态通常表示系统有IO问题僵尸进程表示进程已经退出但它的父进程还没有回收子进程占用的资源短暂的僵尸状态不必理会如果长时间处于僵尸状态有可能应用程序没有正常处理子进程的退出。
我用一个多进程的案例带你分析系统等待 I/O 的 CPU 使用率也就是 iowait%升高的情况。
这个案例是磁盘 I/O 导致了 iowait 升高不过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 信号处理函数的注册就行了