作者:张华 发表于:2016-03-24
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明
( http://blog.csdn.net/quqi99 )
问题
虚机里的某个进程Hang住了,使用”cat /proc/diskstats”命令查看一个SSD硬盘上有很多请求列队。
hung_task_timeout_secs参数与D状态
进程等待IO时如果处于D状态,即TASK_UNINTERRUPTIBLE状态,处于这种状态的进程不处理信号,所以kill不掉,如果进程长期处于D状态,那么肯定不正常,原因可能有二:
- IO路径上的硬件出问题了,比如硬盘坏了(只有少数情况会导致长期D,通常会返回错误);
- 内核自己出问题了。
这种问题不好定位,而且一旦出现就通常不可恢复,kill不掉,通常只能重启恢复了。内核针对这种开发了一种hung task的检测机制,基本原理是:定时检测系统中处于D状态的进程,如果其处于D状态的时间超过了指定时间(默认120s,可以配置),则打印相关堆栈信息,也可以通过proc参数配置使其直接panic (也可能内核发生了其他错误触发crash,crash时间过长越过2分钟它自己的进程也会被hung task误检测出来为D状态)。
1)设置timeout时间:
echo 120 > /proc/sys/kernel/hung_task_timeout_secs
2)设置hung task后是否触发panic
echo 1 > /proc/sys/kernel/hung_task_panic
Hugepage与D状态
逻辑地址到线性地址的转换由分段来做,线性地址到物理地址的转换由分页来做。默认每页是4K,为了减少分页映射表的条目,可以增加页的尺寸,Hugepage因此得名。THP(Transparent Huge Pages)是一个使管理Huge Pages自动化的抽象层(由于实现方式问题,THP会造成内存锁影响性能,尤其是在程序不是专门为大内内存页开发的时候,因为khugepaged会在后台扫描所有进程占用的内存,在可能的情况下会把4K page交换为Huge Pages,在这个过程中需要分配内存锁影响性能)。在redhat6中由khugepaged进程在开机时自动启动,如果想关闭它的话可以:
1, echo "never" > /sys/kernel/mm/redhat_transparent_hugepage/enabled
cat /sys/kernel/mm/redhat_transparent_hugepage/enabled
always madvise [never]
2, or using cmdline, transparent_hugepage=never
never选项将清除TRANSPARENT_HUGEPAGE_FLAG和TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG中的bit值,
内存锁与D状态
有进程为了使其不被转换到SWAP中降低效率会调用mlock来锁定内存,mlock会调用lru_add_drain_all对每个cpu下发work函数lru_add_drain_per_cpu回刷pagevec,然后调用flush_work等待完成,flush_work(kworker)又会调wait_for_completion,继尔调用do_wait_for_common将进程设置为D状态。
static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
return __wait_for_common(x, schedule_timeout, timeout, state);
}
static inline long __sched
__wait_for_common(struct completion *x,
long (*action)(long), long timeout, int state)
{
might_sleep();
spin_lock_irq(&x->wait.lock);
timeout = do_wait_for_common(x, action, timeout, state);
spin_unlock_irq(&x->wait.lock);
return timeout;
}
do_wait_for_common(struct completion *x,
long (*action)(long), long timeout, int state)
{
if (!x->done) {
DECLARE_WAITQUEUE(wait, current);
__add_wait_queue_tail_exclusive(&x->wait, &wait);
do {
if (signal_pending_state(state, current)) {
timeout = -ERESTARTSYS;
break;
}
__set_current_state(state);
spin_unlock_irq(&x->wait.lock);
timeout = action(timeout);
spin_lock_irq(&x->wait.lock);
} while (!x->done && timeout);
__remove_wait_queue(&x->wait, &wait);
if (!x->done)
return timeout;
}
x->done--;
return timeout ?: 1;
}
缺页时的D状态
其他进程缺页时也会进入D状态(缺页的都会是D状态), 缺页中断服务程序do_page_fault会调用down_read(&mm->mmap_sem)加读锁,然后再调__down_read, 再调rwsem_down_failed_common设置D状态:
/* wait to be given the lock */
for (;;) {
//已经获取读锁,跳出死循环
if (!waiter.task)
break;
schedule(); //否则,调度出去,放弃CPU
//调度回来后重设状态
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
//获取锁后改成R状态
tsk->state = TASK_RUNNING;
如何crash的
这个网页(http://www.oenhan.com/rwsem-realtime-task-hung)描述了一种crash的情况,上述的kworker进程进入了D状态,如果这时有另外一个实时进程H的优先级大于kworker的话,采用FIFO的调度模式,且H在大量业务的话那它会一直抢占CPU不释放,那样导致kworker一直得不到调度,直接导致kworker进程D状态触发hung_task_timeout_secs直接死掉。
同一个CPU核上进程有一个H2,而且H1和H2都是被cpu绑定到5核上,H2处理主要业务,H1辅助,H2实时优先级高,H1实时优先级低,当H2压力大占用100% CPU时,H1就得不到调度了。
高业务压力下,H2一直占用CPU,是R状态,H1得不到调度,是S状态。首先是khugepaged扫描时获取写锁,同时包含H2的多个H线程因缺页中断申请读锁,H2处于D状态被schedule让出CPU,H1也恰好缺页中断,获得CPU后申请读锁,排在H2后面,也是D状态。khugepaged释放写锁后,khugepaged先将所有等待读锁的进程拉入读写锁,并将其置成TASK_WAKING状态(参考__rwsem_do_wake函数)。H2先调度回来获取了读锁并完全占用了5核CPU,本来读锁支持并发,但H1此时没有了CPU可供使用,没有调度,一直在schedule打转,虽然进程没有调度,但H1已经被khugepaged拉到读写锁,占用了读锁,一直不释放,khugepaged申请写锁也不能完成,后续更多H缺页中断申请读锁也被阻塞住,D状态达到20s。至于后续能恢复,是因为H2再次因为缺页要申请读锁,排队到队列,进入D状态,schedule让出CPU,H1才能再次得到CPU,完成工作后释放读锁。
解决办法
虚机里的I/O路径是: Application -> File/Block in VM -> virtio-blk driver in VM -> virtio-backend driver in Host -> File/Block in Host -> SSD disk in Host
- VM里面(File/Block in VM -> virtio-blk driver in VM), 因为跑在QEMU里,Guest机的I/O调度算法应该使用elevator=noop
- Hypervisor端(virtio-blk driver in VM -> virtio-backend driver in Host), 两种I/O机制(io=‘native‘ or io=‘threads‘),五种缓存机制(writethrough, writeback, none, directsync, unsafe), 例:<driver name=‘qemu‘ type=‘raw‘ cache=‘writethrough‘ io=‘native‘/>
- Host里面(File/Block in Host -> SSD disk in Host ),由于使用SSD, I/O调度算法应该使用elevator=noop
故需要在grub中添加cmdline启动参数及关闭khugepaged进程:transparent_hugepage=never elevator=noop , 另外,
附件一,如何确认I/O调度算法
[email protected]:~$ sudo lsblk -io KNAME,TYPE,SCHED
KNAME TYPE SCHED
sda disk deadline
sda1 part deadline
sda2 part deadline
sda5 part deadline
sda6 part deadline
sda7 part deadline
sda8 part deadline
sda9 part deadline
sda10 part deadline
sdb disk deadline
[email protected]:~$ cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
http://www.circlingcycle.com.au/Unix-sources/Linux-check-IO-scheduler-and-discard-support.pl.txt
[email protected]:~$ perl ./Linux-check-IO-scheduler-and-discard-support.pl
INFO: File systems and raids
NAME FSTYPE LABEL MOUNTPOINT
sda
├─sda1 [SWAP]
├─sda2
├─sda5 /bak
├─sda6 /data1
├─sda7 /win
├─sda8 /data2
├─sda9 /
└─sda10 /images
sdb
INFO: Block devices
NAME ALIGNMENT MIN-IO OPT-IO PHY-SEC LOG-SEC ROTA SCHED RQ-SIZE
sda 0 4096 0 4096 512 1 deadline 128
├─sda1 0 4096 0 4096 512 1 deadline 128
├─sda2 1024 4096 0 4096 512 1 deadline 128
├─sda5 0 4096 0 4096 512 1 deadline 128
├─sda6 0 4096 0 4096 512 1 deadline 128
├─sda7 0 4096 0 4096 512 1 deadline 128
├─sda8 0 4096 0 4096 512 1 deadline 128
├─sda9 0 4096 0 4096 512 1 deadline 128
└─sda10 0 4096 0 4096 512 1 deadline 128
sdb 0 1048576 2048 512 512 1 deadline 128
INFO: I/O elevator (scheduler) and discard support summary
INFO: Hard Disk sda configured with I/O scheduler "deadline"
INFO: Hard Disk sdb configured with I/O scheduler "deadline" supports discard operation