多线程中使用fork()导致分页

最近和同事一起处理了一个 fuse 的大bug;


首先看堆栈:

Core was generated by `/sf/cluster/bin/pmxcfs'.
Program terminated with signal SIGABRT, Aborted.
#0  0x00007f2debdcc475 in raise () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007f2debdcc475 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f2debdcf6f0 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f2debdc5621 in __assert_fail () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007f2ded4035d2 in fuse_do_release (f=f@entry=0x16148b0, ino=ino@entry=330, path=0x7f2ddc00e040 "/acl.cfg", fi=fi@entry=0x7f2dd73f4b50) at fuse.c:3092
#4  0x00007f2ded403656 in fuse_lib_release (req=0x7f2ddc00c230, ino=330, fi=0x7f2dd73f4b50) at fuse.c:3876
#5  0x00007f2ded4094eb in do_release (req=, nodeid=, inarg=) at fuse_lowlevel.c:1345
#6  0x00007f2ded409f16 in fuse_ll_process_buf (data=0x1614ba0, buf=0x7f2dd73f4d00, ch=) at fuse_lowlevel.c:2441
#7  0x00007f2ded406c5b in fuse_do_work (data=0x7f2dcc0009e0) at fuse_loop_mt.c:117
#8  0x00007f2deca6eb50 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
#9  0x00007f2debe74a7d in clone () from /lib/x86_64-linux-gnu/libc.so.6
#10 0x0000000000000000 in ?? ()

(gdb) 


分析:

1、这个是libfuse库源生的bug,在网上查看了下没有发现有这个bug的相关信息;
2、core分析:
(gdb) p  *(f->id_table.array[3626])
$15 = {name_next = 0x0, id_next = 0x0, nodeid = 330, generation = 0, refctr = 1, parent = 0x7f2dee713090, name = 0x7f2dee54ab20 "acl.cfg", nlookup = 57, 
  open_count = 0, stat_updated = {tv_sec = 0, tv_nsec = 0}, mtime = {tv_sec = 0, tv_nsec = 0}, size = 0, locks = 0x0, is_hidden = 0, cache_valid = 0, 
  treelock = 1, inline_name = "acl.cfg", '\000' }


对应代码:
        pthread_mutex_lock(&f->lock);
        node = get_node(f, ino);
        assert(node->open_count > 0);// node->open_count = 0
        --node->open_count;
        if (node->is_hidden && !node->open_count) {//core中可以看到 is_hidden = 0, 所以在上一次调用该函数的时候,acl.cfg就已经被完全释放了;
                unlink_hidden = 1;
                node->is_hidden = 0;//当node->open_count == 0时,才会设置 node->is_hidden = 0,所以释放已经把最后一个连接去掉了;
        }
        pthread_mutex_unlock(&f->lock);


====

总结:处理这种开源软件bug时,首先是简单分析下core,然后是再到相关网站上看下,基本一些bug之前都有人遇到过;



本来想好好写排查过程的,还有一些有效的排查方法的,但发现 CSDN 好烂,都找不到预览功能在哪里了;


重现方法:
1. 修改代码,多个线程内增加调用system;
2. 修改代码,在fuse_kern_chan_receive里加强assert,如果read正常返回但是缓冲区没改过,即断言
这样可以加大重现概率,基本一天至少可以出4、5个

问题原因:
多线程内fork,父子进程先是共用内存页 准备copy-on-write, A线程等在read /dev/fuse上,A线程的read陷入系统调用 fuse.ko准备将数据写入内存,此时B线程写同一个内存页的内存触发cow,父进程最终使用新页表新内存,但read系统调用将数据写入了旧内存
对应用层的表现是,read返回值正确,但是缓冲区数据没有填充,应用层把缓冲区上旧命令当作新命令执行了两次
原始不改代码的情况下通常表现为两种core,一个是fuse_do_release里open计数断言,另一个是fuse_lib_releasedir里释放内存,其他情况虽然可能命令执行错了,但通常不会表现出core,而是上层访问可能异常;


修改办法:

1、命令结构体申请内存时,用4k对齐函数来申请(偏移量4k对齐,申请内存大小也要4k对齐);

2、把可能的在多线程中调用fork()的系统调用去掉;


多线程服务内应避免使用fork,如system、popen




你可能感兴趣的:(云计算)