5

我編寫了一個代碼來創建一些線程,並且每當其中一個線程完成一個新線程創建以替換它時。由於我無法使用pthread創建大量線程(> 450),因此我使用了克隆系統調用。 (請注意,我知道有這麼多的線程的意思,但這個程序只是爲了強調系統)
由於clone()需要將子線程的堆棧空間指定爲參數,因此我會爲每個線程分配所需的堆棧空間塊,並在線程完成時將其釋放。當一個線程完成時,我發送一個信號給父母通知它相同。
的代碼如下:在多線程(使用克隆)程序中調試分段錯誤

#include <sched.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <signal.h> 
#include <unistd.h> 
#include <errno.h> 

#define NUM_THREADS 5 

unsigned long long total_count=0; 
int num_threads = NUM_THREADS; 
static int thread_pids[NUM_THREADS]; 
static void *thread_stacks[NUM_THREADS]; 
int ppid; 

int worker() { 
int i; 
union sigval s={0}; 
for(i=0;i!=99999999;i++); 
if(sigqueue(ppid, SIGUSR1, s)!=0) 
    fprintf(stderr, "ERROR sigqueue"); 
fprintf(stderr, "Child [%d] done\n", getpid()); 
return 0; 
} 

void sigint_handler(int signal) { 
char fname[35]=""; 
FILE *fp; 
int ch; 
if(signal == SIGINT) { 
    fprintf(stderr, "Caught SIGINT\n"); 
    sprintf(fname, "/proc/%d/status", getpid()); 
    fp = fopen(fname,"r"); 
    while((ch=fgetc(fp))!=EOF) 
    fprintf(stderr, "%c", (char)ch); 
    fclose(fp); 
    fprintf(stderr, "No. of threads created so far = %llu\n", total_count); 
    exit(0); 
} else 
    fprintf(stderr, "Unhandled signal (%d) received\n", signal); 
} 


int main(int argc, char *argv[]) { 
int rc, i; long t; 
void *chld_stack, *chld_stack2; 
siginfo_t siginfo; 
sigset_t sigset, oldsigset; 

if(argc>1) { 
    num_threads = atoi(argv[1]); 
    if(num_threads<1) { 
    fprintf(stderr, "Number of threads must be >0\n"); 
    return -1; 
    } 
} 
signal(SIGINT, sigint_handler); 

/* Block SIGUSR1 */ 
sigemptyset(&sigset); 
sigaddset(&sigset, SIGUSR1); 
if(sigprocmask(SIG_BLOCK, &sigset, &oldsigset)==-1) 
    fprintf(stderr, "ERROR: cannot block SIGUSR1 \"%s\"\n", strerror(errno)); 

printf("Number of threads = %d\n", num_threads); 
ppid = getpid(); 
for(t=0,i=0;t<num_threads;t++,i++) { 
    chld_stack = (void *) malloc(148*512); 
    chld_stack2 = ((char *)chld_stack + 148*512 - 1); 
    if(chld_stack == NULL) { 
    fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t); 
    break; 
    } 
    rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL); 
    if(rc == -1) { 
    fprintf(stderr, "ERROR[%ld]: return code from pthread_create() is %d\n", t, errno); 
    break; 
    } 
    thread_pids[i]=rc; 
    thread_stacks[i]=chld_stack; 
    fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:0x%p]\n", i, thread_pids[i], thread_stacks[i]); 
    total_count++; 
} 
sigemptyset(&sigset); 
sigaddset(&sigset, SIGUSR1); 
while(1) { 
    fprintf(stderr, "Waiting for signal from childs\n"); 
    if(sigwaitinfo(&sigset, &siginfo) == -1) 
    fprintf(stderr, "- ERROR returned by sigwaitinfo : \"%s\"\n", strerror(errno)); 
    fprintf(stderr, "Got some signal from pid:%d\n", siginfo.si_pid); 

    /* A child finished, free the stack area allocated for it */ 
    for(i=0;i<NUM_THREADS;i++) { 
    fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:%p]\n", i, thread_pids[i], thread_stacks[i]); 
    if(thread_pids[i]==siginfo.si_pid) { 
    free(thread_stacks[i]); 
    thread_stacks[i]=NULL; 
    break; 
    } 
    } 
    fprintf(stderr, "Search for child ended with i=%d\n",i); 
    if(i==NUM_THREADS) 
    continue; 
    /* Create a new thread in its place */ 
    chld_stack = (void *) malloc(148*512); 
    chld_stack2 = ((char *)chld_stack + 148*512 - 1); 
    if(chld_stack == NULL) { 
    fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t); 
    break; 
    } 
    rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL); 
    if(rc == -1) { 
    fprintf(stderr, "ERROR[%ld]: return code from clone() is %d\n", t, errno); 
    break; 
    } 
    thread_pids[i]=rc; 
    thread_stacks[i]=chld_stack; 
    total_count++; 
} 
fprintf(stderr, "Broke out of infinite loop. [total_count=%llu] [i=%d]\n",total_count, i); 
return 0; 
} 

我已經使用幾個陣列來跟蹤子進程PID和堆棧區基地址(用於釋放它)。
當我運行這個程序時,它會在某個時間後終止。使用gdb運行告訴我其中一個線程獲得了SIGSEGV(分段錯誤)。但它不給我任何位置,輸出類似於以下內容:

Program received signal SIGSEGV, Segmentation fault. 
[Switching to LWP 15864] 
0x00000000 in ??() 

我試着用下面的命令行下的valgrind運行它:

valgrind --tool=memcheck --leak-check=yes --show-reachable=yes -v --num-callers=20 --track-fds=yes ./a.out 

但它一直沒有任何問題,運行在valgrind下。
我很困惑如何調試這個程序。我覺得這可能是一些堆棧溢出,但增加堆棧大小(高達74KB)並未解決問題。
我唯一的疑問就是爲什麼以及在哪裏出現分段錯誤或者如何調試這個程序。

+0

說實話,我是一無所知克隆功能,但我已經在OpenMP中看到了這一點。您是否嘗試過更改堆棧大小限制,ulimit -s – Anycorn 2010-01-24 18:21:00

回答

1

我想我找到了答案

步驟1

替換此:

static int thread_pids[NUM_THREADS]; 
static void *thread_stacks[NUM_THREADS]; 

通過這樣的:

static int *thread_pids; 
static void **thread_stacks; 

步驟2

在主函數中添加這(檢查參數後):

thread_pids = malloc(sizeof(int) * num_threads); 
thread_stacks = malloc(sizeof(void *) * num_threads); 

步驟3

替換此:

chld_stack2 = ((char *)chld_stack + 148*512 - 1); 

通過這樣的:

chld_stack2 = ((char *)chld_stack + 148*512); 

在這兩個地方你使用它。

我不知道它是否真的是你的問題,但經過測試,我沒有得到任何分段錯誤。順便說一句,我只使用超過5個線程時出現分段錯誤。

希望我幫助!

編輯: 1000個線程測試,完美運行

EDIT2:解釋爲什麼thread_pids和thread_stacks的靜態分配導致錯誤。

做到這一點的最好方法是舉一個例子。

假定num_threads = 10;

的問題發生在以下代碼:

for(t=0,i=0;t<num_threads;t++,i++) { 
... 

thread_pids[i]=rc; 
thread_stacks[i]=chld_stack; 

... 
} 

在這裏,你嘗試訪問存儲器,它不屬於你(0 < = I < = 9,但兩個陣列的大小爲5)。這可能會導致分段錯誤或數據損壞。如果兩個數組一個接一個地分配,則可能會發生數據損壞,從而導致寫入另一個數組。如果你在你沒有分配的內存中寫入(靜態或動態),可能會發生分段。

您可能很幸運,根本沒有任何錯誤,但代碼肯定不安全。

關於不對齊的指針:我想我不必解釋更多比在我的評論。

+0

hi George。我在amd64上嘗試過原始代碼,它沒有得到分割。我很好奇你的系統是什麼問題。你能簡單解釋一下嗎?謝謝 – Anycorn 2010-01-24 19:11:11

+0

嗨喬治,你的解決方案不適合我:(順便說一句,你能解釋爲什麼這些變化爲你工作? – Sukanto 2010-01-25 04:09:20

+0

嗯,我注意到,我沒有任何問題<= 5線程,但超過5線程我總是得到分段錯誤 錯誤是你沒有足夠的內存分配給每個線程(你靜態分配它)與訪問未分配的地址在你的代碼中的結果 也通過減去1 chld_stack + 148 * 512你會得到一個無效的地址(應該是字對齊的,因爲地址是奇數),這可能會導致分段錯誤或堆棧損壞 我不知道你是不幸還是我幸運,但正如我所說的那樣對我有益。 – George 2010-01-25 10:54:43

3

發現實際問題。
當工作線程使用sigqueue()向父進程發信號時,父進程有時會立即獲取控件,並在子進程執行返回語句之前釋放堆棧。當同一個子線程使用return語句時,它會在堆棧損壞時導致分段錯誤。
爲了解決這個我換成

exit(0) 

,而不是

return 0;