05 2011存档

年假和菠萝

| | Comments (0) | TrackBacks (0)
前几周,公司的座位不够了,新人也都是临时坐在出差同学的工位上。

同事A:下周一好几个同事出差回来,另外还要来新人,那不是挤爆了
同事B:没辙了......要不颁布政策,鼓励大家下周一休假......凡是下周休年假的,休一天送一天,买一送一......


本周,终于搬了新地方,工位不挤了,但是有一点刚装修后的异味儿。于是我去买了一个坏的菠萝(两块钱一个),用来吸味。切成4份,放在桌上,同事看见了,闻了闻菠萝,由于坏的部分已经去掉,闻着跟好菠萝一样
同事M: 你买菠萝来作甚?
我: 吸味儿啊
同事M: 那多贵啊
我: 不要紧,坏的,便宜,两块一个
同事M: 喔,闻着挺好的......我总算知道饭店里的拼盘是怎么弄的了

捉虫记

| | Comments (0) | TrackBacks (0)
线上有应用使用了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。

有同事问到我:使用cp命令时,如果发现要被覆盖的文件(二进制可执行文件)正在运行,cp会报错,cp命令是怎么知道该文件正在执行的?

我第一个想到的是:可能ioctl可以检查文件。可惜我猜错了,看了一下cp的源码,原来如果一个文件正在运行,另一个进程再open以获得写权限的话,这个open本身就会失败,返回-1,errno为ETXTBSY。
那么cp -f 为什么又可以?因为cp -f会先把目标文件(要被覆盖的文件)删掉,然后将源文件rename为目标文件名。

从内核代码看,当运行一个二进制文件时
sys_execve()
  do_execve()
    open_exec()
      deny_write_access()
这里的deny_write_access会把文件对应inode的i_writecount成员减1,通常i_writecount的值就变成-1了(初始为0)
这时候再有进程想以写模式open:
do_sys_open()
  do_filp_open()
    path_openat()
      do_last()
        nameidata_to_filp()
          __dentry_open()
            __get_file_write_access()
              get_write_access()
get_write_access会发现inode的i_writecount成员为负数了,所以直接返回 -ETXTBSY

关于存档

This page is an archive of entries from 05 2011 listed from newest to oldest.

04 2011 is the previous archive.

06 2011 is the next archive.

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