1 问题描述

最近搭建的一个linux最小系统在运行到241秒时在控制台自动打印如下图信息,并且以后每隔120秒打印一次。
这里写图片描述

仔细阅读打印信息发现关键信息是“hung_task_timeout_secs”,第一次遇到这样的问题,首先百度…

通过翻看多个网友的博客,发现这是linux kernel的一个bug。大家对这个问题的解释也都比较一致,摘抄一段:

By default Linux uses up to 40% of the available memory for file system caching.
After this mark has been reached the file system flushes all outstanding data to disk causing all following IOs going synchronous.
For flushing out this data to disk this there is a time limit of 120 seconds by default.
In the case here the IO subsystem is not fast enough to flush the data withing 120 seconds.
This especially happens on systems with a lot of memory.
The problem is solved in later kernels。

翻译过来就是:一般情况下,linux会把可用内存的40%的空间作为文件系统的缓存。当缓存快满时,文件系统将缓存中的数据整体同步到磁盘中。但是系统对同步时间有最大120秒的限制。如果文件系统不能在时间限制之内完成数据同步,则会发生上述的错误。这通常发生在内存很大的系统上。系统内存大,则缓冲区大,同步数据所需要的时间就越长,超时的概率就越大。

2 解决办法

网友们提供的解决方案大致有3种,分别对3种方案的效果进行验证测试。

2.1 缩小文件系统缓存大小

此种方案是降低缓存占内存的比例,比如由40%降到10%,这样的话需要同步到磁盘上的数据量会变小,IO写时间缩短,会相对比较平稳。

文件系统缓存的大小是由内核参数vm.dirty_ratio 和 vm.dirty_backgroud_ratio控制决定的。

vm.dirty_background_ratio指定当文件系统缓存脏页数量达到系统内存百分之多少时(如5%)就会触发pdflush/flush/kdmflush等后台回写进程运行,将一定缓存的脏页异步地刷入外存。

vm.dirty_ratio则指定了当文件系统缓存脏页数量达到系统内存百分之多少时(如10%),系统不得不开始处理缓存脏页(因为此时脏页数量已经比较多,为了避免数据丢失需要将一定脏页刷入外存),在此过程中很多应用进程可能会因为系统转而处理文件IO而阻塞。

通常情况下,vm.dirty_ratio的值要大于vm.dirty_background_ratio的值。

下面说一下修改这两个参数的流程及效果。

(1)首先查看系统当前的vm.dirty_ratio 和 vm.dirty_ background_ratio的值。

在shell命令行中输入如下指令:

sysctl -a | grep dirty
  • 1

这里写图片描述
由上图可知,当前环境下,vm.dirty_ratio=20 ,vm.dirty_ background_ratio=10。在此情况下,会出现上述问题。

(2)修改vm.dirty_ratio和vm.dirty_ background_ratio的值。
把vm.dirty_ratio修改为10 ,vm.dirty_background_ratio修改为5。在命令行输入如下命令:

./sbin/sysctl -w vm.dirty_ratio=10
./sbin/sysctl -w vm.dirty_background_ratio=5
  • 1
  • 2

这里写图片描述
(3)查看修改是否成功。

在命令行输入(1)中的指令即可。
这里写图片描述
由上图可知,参数修改成功。

(4)使参数修改立即生效。

在命令行输入如下指令即可。

sysctl  -p
  • 1

(5)观察测试结果。

经过以上操作,缩减文件系统缓存之后,上述问题成功解决。

2.2 修改系统IO调度策略

Linux的IO共有三种调度器:CFQ、noop、deadline。每个调度器都有其优点。

CFQ (Completely Fair Scheduler(完全公平调度器))(cfq):它是许多Linux 发行版的默认调度器;它将由进程提交的同步请求放到多个进程队列中,然后为每个队列分配时间片以访问磁盘。

Noop调度器(noop):基于先入先出(FIFO)队列概念的Linux内核里最简单的I/O调度器。此调度程序最适合于SSD。

截止时间调度器(deadline):尝试保证请求的开始服务时间。

Linux发行版的默认采用的是cfq调度器。此方案就是把cfq调度器修改为最简单的noop调度器。

下面说一下修改调度器的流程。

(1)查看当前采用的调度器。

在命令行中输入如下指令:

cat /sys/block/sda/queue/scheduler
  • 1

这里写图片描述
由内核返回信息可知,当前采用的是cfq调度器。

(2)修改调度器。

在命令行中输入如下指令:

echo noop >/sys/block/sda/queue/scheduler
  • 1

这里写图片描述
(3)查看修改是否成功。

在命令行中输入如(1)的指令。
这里写图片描述
由内核返回信息可知,调度器修改成功。

调度器的修改是立即生效的,不必重启内核。

(4)观察测试结果。

修改完成后,让系统继续运行一段时间。上述问题依然存在。所以此方案对本文的案例不起作用。对于其他案例是否起作用需要自己按照上述方式测试才能得知。

2.3 取消120秒时间限制

此方案就是不让系统有那个120秒的时间限制。文件系统把数据从缓存转到外存慢点就慢点,应用程序对此延时不敏感。就是慢点就慢点,我等着。实际上操作系统是将这个变量设为长整形的最大值。

下面说一下内核hung task检测机制由来。我们知道进程等待IO时,经常处于D状态,即TASK_UNINTERRUPTIBLE状态,处于这种状态的进程不处理信号,所以kill不掉,如果进程长期处于D状态,那么肯定不正常,原因可能有二:

1)IO路径上的硬件出问题了,比如硬盘坏了(只有少数情况会导致长期D,通常会返回错误);

2)内核自己出问题了。

这种问题不好定位,而且一旦出现就通常不可恢复,kill不掉,通常只能重启恢复了。
内核针对此种情况开发了一种hung task的检测机制,基本原理是:定时检测系统中处于D状态的进程,如果其处于D状态的时间超过了指定时间(默认120s,可以配置),则打印相关堆栈信息,也可以通过proc参数配置使其直接panic。

如何修改或者取消120秒的时间限值呢。120秒的时间限值由内存参数kernel.hung_task_timeout_secs决定的。直接像方案一那样修改此内核参数的值就可。如果kernel.hung_task_timeout_secs的值设置为0,那就是把此种设置为长整型的最大值。

下面说一下修改调度器的流程。

(1)查看当前hung_task_timeout_secs值。

在命令行中输入如下指令:

sysctl -a | grep hung_task_timeout_secs
  • 1

这里写图片描述

有内核返回信息,可知当前设置的hung_task超时时间为120秒。

(2)修改hung_task_timeout_secs值。

把hung_task_timeout_secs的值修改为0,在命令行中输入如下指令:


./sbin/sysctl -w kernel.hung_task_timeout_secs=0
  • 1
  • 2

这里写图片描述

(3)查看修改是否成功。

在命令行输入(1)中的指令即可。
这里写图片描述

(4)使参数修改立即生效。

在命令行输入如下指令即可。

sysctl  -p
  • 1

(5)观察测试结果。
经过以上操作,取消120秒时间限值之后,上述问题成功解决。

2.4 总结
经过上边的测试,可知对与本案例缩减文件系统缓冲大小和取消120秒时间限值均可以解决问题。但是取消120秒的时间限值会允许系统不可切换任务的出现。综合考虑决定采用方案1,即缩减文件系统的缓冲区大小。

3 永久修改内核参数

在上述方案中采用sysctl可以修改内核参数,但是这只是临时修改,上电重启后又会恢复回之前的参数。那么如何才能够永久修改内核参数呢?

可以修改系统信息配置文件sysctl.conf,此配置文件在/etc目录下。打开配置文件在最后添加如下两行代码:

vm.dirty_background_ratio=5
vm.dirty_ratio=10
  • 1
  • 2

这里写图片描述
保存后重启系统,查看配置是否成功。

如果系统没有sysctl.conf文件,就像我的最小linux系统一样,则可以自己创建sysctl.conf。

在/etc目录下,采用vi指令:vi sysctl.conf新建sysctl.conf文件,然后输入如下代码后保存退出。

重启之后查看vm.dirty_ratio和vm.dirty_background_ratio的值,发现又恢复成之前的了。

这是因为开机启动时系统没有读取sysctl.conf文件进行配置。可以通过修改启动文件来解决。

以我使用的linux系统为例,启动文件是/etc/init.d/rcS。采用指令vi /etc/init.d/rcS打开rcS文件,在最末尾添加如下代码:./sbin/sysctl -p
这里写图片描述

保存后退出。reboot重启可以看到内核打印信息中有如下信息:
这里写图片描述
进入控制台后,查看vm.dirty_ratio和vm.dirty_background_ratio的值,可知内核参数永久修改成功。

【参考】
1、Linux Kernel Crash–hung_task_timeout_secs 作者:李子无为
http://blog.csdn.net/napolunyishi/article/details/17576739
2、How to fix hung_task_timeout_secs and blocked for more than 120 seconds problem 作者:skate
http://blog.csdn.net/wyzxg/article/details/44236263
3、关于修改 sysctl.conf,如何使该文件在系统重启之后生效 作者:dagebudagegeda
http://blog.csdn.net/u010616985/article/details/44563931
4、文件系统缓存dirty_ratio与dirty_background_ratio两个参数区别 作者:vincent
http://blog.sina.com.cn/s/blog_448574810101k1va.html
5、一次内核hung task分析 作者:humjb_1983
http://blog.chinaunix.net/uid-14528823-id-4406510.html