捉虫记

线上有应用使用了ext4文件系统的no journal模式(mkfs.ext4 -o ^has_journal /dev/sda),最近反馈有目录丢失,dmesg里报错:
EXT4-fs error (device sda7): ext4_lookup: deleted inode referenced: 26214657

上周五我们用e2image把线上的硬盘镜像出来,然后fsck sda.img -fy对镜像文件进行修复,发现这些corrupt的目录(54个子目录)在inode table对应的内容全为空(不仅是nlink为0,连 ctime/atime/mtime等都是空),这不是rmdir能造成的(rmdir会修改 dtime而不是清空inode table里对应的那一整个inode),所以此bug应该不是rmdir造成,这与线上的现象一致(从同事的反馈,线上应用只会在硬盘快满时才rmdir,而出现bug的时候硬盘空间还很宽裕)。

于是我们怀疑这种全空是由类似memset的内核代码操作造成的,便寻找 fs/ext4/ 下调用了memset的所有地方,其中 fs/ext4/inode.c 里 __ext4_get_inode_loc 函数里的一句尤其刺眼:

memset(bh->b_data, 0, bh->b_size);

这一句的调用时机是:当前inode如果是它所在inode table的buffer里唯一的一个 inode,则调用memset。线上corrupt的目录都是它父目录下的第255个目录,它们确实是满足这个调用时机的,故而此处嫌疑最大。问题很可能出现在mkdir上。

根据这个判断我们设计了一个重现方法:
首先,启动一个程序,重复的创建目录和文件(模拟线上squid创建的那个目录结 构),为了增加race,10个线程同时去mkdir,然后再删 除全部目录,再remount 硬盘,周而复始。
另外,还启动一个程序,重复的让系统drop_cache
最后,启动多个nbench(一个测试CPU和内存速度的测试工具),增加系统的CPU压力

使用这个重现方法,三管并发,在开发机上不到1小时这个bug便出现了。

于是我们在kernel source里插入褉子,最后追踪到是系统在writeback时会调用 ext4_write_inode ,而它里面错误的调用了 ext4_get_inode_loc (应该调 __ext4_get_inode_loc且第三个参数为0),memset掉了 inode table buffer_head的内容,回头再看upstream的code,这个bug原来已经被google的同学在一年前fix了:(commit 8b472d739b2ddd8ab7fb278874f696cd95b25a5e)。

ext4的no journal本来就是google贡献的,估计他们已经用开了,所以早就已经解决了这个问题,而我们的kernel tree上还没有这个fix。


相关文章

分类

留言:

关于文章

This page contains a single entry by DongHao published on 05 19, 2011 2:06 PM.

cp报错"Text file busy" was the previous entry in this blog.

年假和菠萝 is the next entry in this blog.

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