修复ext4 aio bug一个
皓庭同学在给一台测试机做压力的时候出现了kernel panic:
<4>[ 223.202997] RIP: 0010:[<ffffffff81194e79>] [<ffffffff81194e79>] iput+0x69/0x70
......
<4>[ 223.210475] [<ffffffffa007de6d>] ext4_free_io_end+0x2d/0x40 [ext4]
<4>[ 223.210864] [<ffffffffa007e02c>] ext4_end_aio_dio_work+0xac/0xf0 [ext4]
<4>[ 223.211277] [<ffffffffa007df80>] ? ext4_end_aio_dio_work+0x0/0xf0 [ext4]
<4>[ 223.211693] [<ffffffff8108cc50>] worker_thread+0x170/0x2a0
<4>[ 223.212033] [<ffffffff810925c0>] ? autoremove_wake_function+0x0/0x40
<4>[ 223.212425] [<ffffffff8108cae0>] ? worker_thread+0x0/0x2a0
<4>[ 223.227238] [<ffffffff81092256>] kthread+0x96/0xa0
<4>[ 223.241935] [<ffffffff8100c10a>] child_rip+0xa/0x20
<4>[ 223.256518] [<ffffffff810921c0>] ? kthread+0x0/0xa0
<4>[ 223.271085] [<ffffffff8100c100>] ? child_rip+0x0/0x20
重现的环境和步骤都很简单:只要在redhat linux-2.6.32-279.14.1 kernel上进一个使用ext4的目录一跑:
stress --cpu 2 --io 2 --vm 1 --vm-bytes 128M --hdd 2 --timeout 1d
几分钟后就出现。
这个stress工具果然是神器。
由于重现步骤简单,我也没有太多需要绞尽脑汁的猜测和bug重现工作,就是加trace_printk一点一点调试,最后找到了原因:
stress测试工具是用DIRECT_IO发起的aio(异步IO)请求,在ext4里对于DIRECT_IO请求,都会用ext4_init_io_end初始化一个ext4_io_end_t结构,初始化时调用igrab对该inode里面的i_count进行递增;
struct inode *igrab(struct inode *inode)
{
spin_lock(&inode_lock);
if (!(inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)))
__iget(inode);
else {
/*
* Handle the case where s_op->clear_inode is not been
* called yet, and somebody is calling igrab
* while the inode is getting freed.
*/
printk("%s: %lx\n", __func__, inode->i_state);
inode = NULL;
}
spin_unlock(&inode_lock);
return inode;
}
这些aio结束后,会调用ext4_free_io_end进而调用iput把inode的i_count递减,等到i_count递减到0时,就用iput_final把该inode置为I_CLEAR,意思是该inode可被清除了(当内存不够的时候会把这种I_CLEAR的inode占的内存空间回收)。
但是,如果一个文件已经被rm了,即已经调用了generic_delete_inode,那么inode就已经置上了I_FREEING,此时再调用igrab时,由于第一个判断无效,inode里的i_count就不会被加1了,i_count会一直保持0!等到aio结束后,iput被调用了多次,第一次触发iput_final把inode置上I_CLEAR,第二次就坏了,触发BUG_ON
void iput(struct inode *inode)
{
if (inode) {
BUG_ON(inode->i_state == I_CLEAR);
if (atomic_dec_and_lock(&inode->i_count, &inode_lock))
iput_final(inode);
}
}
因为一般对同一个inode,调了iput_final就不能再调iput了,因为你已经final了、没了。
说来说去,就是igrab似乎不该做那个判断,或者,干脆不要用igrab,用别的计数方式。翻了一下upstream,得,又已经被fix了,commit f7ad6d2e9201a6e1c9ee6530a291452eb695feb8, Ted两年前就已经fix了,抛弃了igrab改为自己管理计数。这个fix backport到rhel-2.6.32-279 kernel还需要一些修改,好在有文卿帮我review代码。
====== 2013.1.18 ======
我们打在rhel 2.6.32-279内核的修复patch是这个 https://github.com/taobao/taobao-kernel/blob/master/patches.taobao/ext4-handle-writeback-of-inodes-which-are-being-reed.patch
很不好意思,patch的标题写错了,还请忽略标题,我回头再改,patch内容是对的。
相关文章
- ext4 bigalloc 答疑 - 02 21, 2013
- 修复ext4 bug一个 - 12 25, 2012
- CLSF 2012讨论会纪要(二) - 10 15, 2012
"i_count就行递增"==>"i_count递增"
@awp47,非常感谢,已经改过来了
你好,这个问题彻底解决了吗?能将patch发出来吗?我们在使用stress给风河加压时也碰到同样的问题,非常感谢!
我们的patch是这个 https://github.com/taobao/taobao-kernel/blob/master/patches.taobao/ext4-handle-writeback-of-inodes-which-are-being-reed.patch (patch的标题有错误,内容正确)已经在文章里加上了
非常感谢,试试先!