目录
僵尸进程不断生成,导致服务器hang住,真实的案例在眼前发生,对于线上生产环境问题,秉着应用恢复正常为第一要义,凭借高超的重启技术,先重启服务器然后重启应用恢复,感叹重启大法好,一切又回归了平静,在充实而忙碌的日子里等待下一次的异常后的重启操作。。。一次又一次,重启操作越来越熟练,僵尸进程还是那些进程。终于有一天,重启熟练度达成新成就99%,没有了上升空间...这天,僵尸进程又来了....
一、什么是进程?
进程是指在系统中正在运行的一个应用程序,是资源拥有的基本单位,且每个进程拥有独立的地址空间。一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程是CPU独立运行和独立调度的基本单位。
二、进程的状态
top 和 ps 是最常用的查看进程状态的工具,从 top 的输出示例,S 列(也就是 Status 列)表示进程的状态。如下出现R、D、Z、S、I 等几个状态:
[root@localhost ~]# top
PID 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 top
620 root 20 0 37280 33676 908 D 0.3 0.4 0:00.01 app
1 root 20 0 160072 9416 6752 S 0.0 0.1 0:37.64 systemd
1896 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 devapp
2 root 20 0 0 0 0 S 0.0 0.0 0:00.10 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
7 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 状态的进程却不会。
除了以上 5 个状态,进程还包括下面这 2 个状态。
第一个是 T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你用 fg 命令,恢复到前台运行)。而当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。
另一个是 X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。
三、不可中断进程和僵尸进程
1、不可中断状态(D状态)
进程不可中断状态其实是为了保证进程数据与硬件状态一致,正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不可中断状态进程,我们一般可以忽略。但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,就很有可能引起I/O 等性能问题。
2、僵尸进程(Z状态)
僵尸进程,在多进程应用经常会碰到。
正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而且子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
如果父进程没有通过系统调用 wait() 或者 waitpid() 等待子进程结束,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。
通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。
系统所能使用的进程号是有限的,大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建。该为僵尸进程的危害,应当避免 。
两个概念:进程组和会话。它们用来管理一组相互关联的进程。
进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员;
会话是指共享同一个控制终端的一个或多个进程组。
四、iowait升高分析
磁盘 I/O 导致了 iowait 升高,不过, iowait 高不一定代表 I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。
一般步骤如下:
①用dstat ,pidstat命令同时查看cpu和i/o对比情况(如 dstat 1 10 间隔1秒输出10组数据),通过结果可以发现iowait升高时,磁盘读请求(read)升高,推断iowait升高是磁盘读导致
②定位磁盘读的进程,等待 I/O 的进程一般是不可中断状态,使用top命令查看处于不可中断状态(D)的进程PID 或 用 ps 命令找到的 D 状态(即不可中断状态)的进程,多为可疑进程。
③查看对应进程的磁盘读写情况,使用pidstat命令,加上-d参数,可以看到i/o使用情况(如 pidstat -d -p <pid> 1 3),发现处于不可中断状态的进程都没有进行磁盘读写
④继续使用pidstat命令,查看所有进程的i/o情况(pidstat -d 1 20),可以定位到进行磁盘读写的进程。我们知道进程访问磁盘,需要使用系统调用
⑤使用strace查看进程的系统调用 strace -p <pid>,发现报了 strace:attach :ptrace(PTRACE_SIZE,6028):Operation not peritted,说没有权限,使用的root权限,所以这个时候就要查看进程的状态是否正
⑥ps aux | grep <pid> 发现进程处于Z状态,已经变成了僵尸进程,所以不能进行系统调用分析了
⑦既然top和pidstat都不能找出问题,使用基于事件记录的动态追踪工具
⑧在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告,观察调用栈信息,检查是否有磁盘读操作
⑨从代码层面分析,究竟是哪里出现了直接读请求。
五、僵尸进程分析
僵尸进程是因为父进程没有回收子进程的资源而出现的,那么,要解决掉它们,找出父进程,然后在父进程里解决。
①使用 pstree 找出父进程后,kill父进程,僵尸进程将会被init进程接管回收。
②查看父进程的代码,接着查看 app 应用程序的代码,看看子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,抑或是,有没有注册 SIGCHLD 信号的处理函数。