1. 程式人生 > >多執行緒中使用fork()導致分頁

多執行緒中使用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 (

[email protected]=0x16148b0, [email protected]=330, path=0x7f2ddc00e040 "/acl.cfg", [email protected]=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=<optimized out>, nodeid=<optimized out>, inarg=<optimized out>) at fuse_lowlevel.c:1345
#6  0x00007f2ded409f16 in fuse_ll_process_buf (data=0x1614ba0, buf=0x7f2dd73f4d00, ch=<optimized out>) 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' <repeats 24 times>}


對應程式碼:
        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