修复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代码。

还要感谢皓庭同学帮忙找到了这么快速的重现方法,以及余峰老哥推荐的stress这个生猛工具。


====== 2013.1.18 ======

很不好意思,patch的标题写错了,还请忽略标题,我回头再改,patch内容是对的。


相关文章

分类

5 Comments

awp47 said:

"i_count就行递增"==>"i_count递增"

DongHao Author Profile Page said:

@awp47,非常感谢,已经改过来了

国标37 said:

你好,这个问题彻底解决了吗?能将patch发出来吗?我们在使用stress给风河加压时也碰到同样的问题,非常感谢!

DongHao Author Profile Page said:

我们的patch是这个 https://github.com/taobao/taobao-kernel/blob/master/patches.taobao/ext4-handle-writeback-of-inodes-which-are-being-reed.patch (patch的标题有错误,内容正确)已经在文章里加上了

国标37 said:

非常感谢,试试先!

留言:

关于文章

This page contains a single entry by DongHao published on 01 15, 2013 4:28 PM.

2012年读书 was the previous entry in this blog.

压测Cubieboard is the next entry in this blog.

Find recent content on the main index or look in the 存档 to find all content.