CPU和IO空闲但负载较高问题排查这里写自定义目录标题

  • 背景
  • 问题分析
  • 系统平均负载的计算
  • 为什么会存在大量`D`状态的进程
  • 处理`D`状态的进程


背景

一台线上服务器,监控告警显示服务器负载较高,达到了100多。登录机器使用top命令查看,CPU和IO使用率均较低。同时,发现系统中存在较多pspidof的进程,且他们的进程状态均为D,使用kill命令无法杀死这些进程。

问题分析

初步怀疑是这些进程状态为D的大量pspidof的进程导致的负载较高。

通过man ps查询进程的状态描述信息:

D    uninterruptible sleep (usually IO)
R    running or runnable (on run queue)
S    interruptible sleep (waiting for an event to complete)
T    stopped, either by a job control signal or because it is being traced
W    paging (not valid since the 2.6.xx kernel)
X    dead (should never be seen)
Z    defunct ("zombie") process, terminated but not reaped by its parent

根据描述,进程状态D表示uninterruptible sleep (usually IO),即不可中断的休眠,通常是IO操作导致。

查阅资料得知,处于D状态的进程通常是在等待 IO,比如磁盘 IO,网络 IO,其他外设 IO。处在这种状态的进程不接受外来的任何信号,故而无法通过kill来杀死它。

那么,为什么处于D状态的进程会导致机器平均负载升高呢?

系统平均负载的计算

以单CPU是为例,比如过去的平均一分钟里面,判断系统处于运行或者等待状态的进程数则表示系统的平均负载。但是在linux系统下稍有不同,那些处于IO等待状态的进程也会被纳入计算。所以CPU利用率和平均负载很不同,在大部分进程都在做IO处理的时候,可能CPU利用率很低,但是平均负载很大。

在我们的场景中,存在大量处于D状态的进程,这些进程本质上都处于IO等待状态,会被纳入到平均负载的计算当中。故而,到最后了机器平均负载升高。

为什么会存在大量D状态的进程

处于D状态的进程通常是在等待 IO,出现大量进程处于D状态通常有两种原因,一是IO设备故障,二是内核IO操作死锁。在我们的场景中就是由于集团监控端和我们自己的监控端都在频繁调用pspidof,其内部异常导致内核IO操作死锁。

处理D状态的进程

首先需要判断是否是IO设备故障,如果是IO设备故障,只能尝试修复IO设备。

如果是内核IO操作死锁,可以尝试杀死所有异常进程看是否能够释放锁。

由于处理D状态的进程没法直接kill,可以尝试修改进程状态,然后kill掉进程,具体操作如下:

1、编写内核模块

源码文件: killd.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
MODULE_LICENSE("BSD");
static int pid = -1;
module_param(pid, int, S_IRUGO);
static int killd_init(void)
{
    struct task_struct * p;
    // force D status process to death
    for_each_process(p){
        if(p->pid == pid){
            set_task_state(p, TASK_STOPPED);
            return 0;
        }
    }
    return 0;
}
static void killd_exit(void)
{
    // do nothing
}
module_init(killd_init);
module_exit(killd_exit);

2、编译内核模块

2.1 Makefile文件:

bj-m := killd.o

2.2 获取kernel名称:

uname -r

2.3 编译:

make -C /lib/modules/<kernel名称>/build M=`pwd` modules

3 加入模块的时候传入进程号,将进程状态转换为stopped状态

3.1 创建pids文件,进程号按行加入到文件中
3.2 创建stop.sh文件:

cat pids | while read line
do
    echo $line
    insmod ./killd.ko pid=$line && rmmod killd
    kill -9 $line
done

3.3 执行

chmod 755 stop.sh && ./stop.sh

通过杀死uninterruptible sleep状态进程的方式,极有可能无法解决IO操作的死锁问题,甚至可能会引起其他的一些系统异常,最简单的有效的方式是reboot重启机器。