2012-09-23 23 views
2

我已經蒸餾了一個更大的程序,直到底部顯示的代碼。在Valgrind的運行這個程序最終會報告這個錯誤:glibc,退出時關閉FILE *之間的可能競態條件?

 
==7234== Invalid read of size 4 
==7234== at 0x34A7275FC8: [email protected]@GLIBC_2.2.5 (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7275EA1: new_do_write (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7276D44: [email protected]@GLIBC_2.2.5 (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7278DB6: _IO_flush_all_lockp (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7278F07: _IO_cleanup (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7238BBF: __run_exit_handlers (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A7238BF4: exit (in /usr/lib64/libc-2.15.so) 
==7234== by 0x34A722173B: (below main) (in /usr/lib64/libc-2.15.so) 
==7234== Address 0x542f2e0 is 0 bytes inside a block of size 568 free'd 
==7234== at 0x4A079AE: free (vg_replace_malloc.c:427) 
==7234== by 0x34A726B11C: [email protected]@GLIBC_2.2.5 (in /usr/lib64/libc-2.15.so) 
==7234== by 0x40087C: writer (t.c:22) 
==7234== by 0x34A7607D13: start_thread (in /usr/lib64/libpthread-2.15.so) 
==7234== by 0x34A72F167C: clone (in /usr/lib64/libc-2.15.so) 


從上面的輸出,這似乎是發生了什麼:

  • 的main()返回,並開始運行退出處理程序關閉所有FILE *
  • 作家()的線程,仍在運行,喚醒,關閉文件*
  • 退出處理程序試圖訪問封閉FILE *,也就是現在的無效/ free()的倒是

據我所知,測試程序沒有做任何未定義的事情,但我很樂意在這方面出錯。

Valgrind鉤入各種函數,所以它可能是valgrind錯誤而不是glibc。

  • 這是一個glibc錯誤?
  • 或者它是一個valgrind錯誤?

  • 有關如何確定它是valgrind還是glibc的任何想法?

TC:

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 

void *test(void *arg) 
{ 
    return NULL; 
} 
void *writer(void *arg) 
{ 
    for(;;) { 
     char a[100]; 
     FILE *f = fopen("out", "w"); 

     if(f == NULL) 
      abort(); 

     fputs("Test", f); 

     if(fgets(a, 100, stdin)) 
      fputs(a, f); 
     fclose(f); //line 22 
    } 

    return NULL; 
} 

int main(int argc, char *argv[]) 
{ 
    pthread_t tid1,tid2; 


    pthread_create(&tid1, NULL, writer, NULL); 
    pthread_create(&tid2, NULL, test, NULL); 
    pthread_join(tid2, NULL); 
    //pthread_join(tid1, NULL); //no bug if we wait for writer() 
    return 0; 
} 
//compile: gcc t.c -g -pthread 

可能需要幾分鐘觸發從Valgrind的一個錯誤,用:

while [ true ]; do 
    echo test |valgrind --error-exitcode=2 ./a.out || break 
done 

環境:Fedora的17,glibc的2.15,GCC-4.7.0 -5,內核3.5.3-1.fc17.x86_64,valgrind-3.7.0-4

+0

這裏真正的問題是什麼?那個valgrind抱怨?你已經知道正確的處理是加入tid1(可能在鼓勵它終止一個標誌後)。 – walrii

+0

@walrit問題是valgrind,glibc或程序有bug,我想知道哪一個。這是一個簡單的測試用例 - 在退出時暫停所有線程並不總是可行的 - 如果退出多線程並不會導致運行時間垃圾內存,那麼這將是一個加號。 – nos

回答

4

您有一個競爭條件。你有一個線程調用exit,它被記錄爲關閉所有打開的stdio流。然後你有另一個線程,可能在exit關閉它之後訪問這樣的流。關閉後您不能訪問FILE* - 允許指向垃圾。

如果一個線程執行的某些操作使得調用exit不安全,則必須確保您不要調用exit。這真的很簡單。

+0

我會同意這可能是一個競爭條件。輸出結果似乎相反,即運行時在關閉後訪問FILE *。如果不能阻止這樣的事情,我會質疑是否需要所有複雜的鎖定glibc。 – nos

+1

'glibc'鎖定不可能阻止這個 - 指針變得無效。您可以實施全局計數的事件,以防止退出。然後設置一個「我想退出」的標誌。等待計數降至零,然後致電退出。其他線程在開始這樣的事情之前檢查標誌,並增加計數,而不能調用退出。 (通常,乾淨的關閉不值得麻煩,你應該乾淨地關閉需要乾淨關閉的東西,然後調用'_exit'。) –

+1

@David:POSIX要求'exit'是線程安全的。潛在的不安全行爲是調用一個stdio函數,該函數在'exit'期間作用於流上。我沒有看到任何地方''exit'的原子性是明確的。然而,至少從實施質量的角度來看,應該不可能觀察到「出口」關閉了任何流的點,但該過程並沒有終止。在執行stdio時也很容易實現:只是在不實際關閉它們的情況下刷新流(最終'_exit'將會關閉fds),並在刷新後保持鎖定狀態以防止進一步的io。 –