5

J'ai écrit un code pour créer des threads et chaque fois qu'un des threads se termine, un nouveau thread est créé pour le remplacer. Comme je n'étais pas capable de créer un très grand nombre de threads (> 450) en utilisant pthreads, j'ai utilisé l'appel système clone à la place. (S'il vous plaît noter que je suis conscient de l'implication d'avoir un tel nombre de threads, mais ce programme est destiné à ne souligner que le système).
Comme clone() requiert que l'espace de pile pour le thread enfant soit spécifié en tant que paramètre, je place le bloc d'espace de pile requis pour chaque thread et le libère lorsque le thread se termine. Quand un thread finit, j'envoie un signal au parent pour l'avertir de la même chose.
Le code est donné ci-dessous:Débogage d'une erreur de segmentation dans un programme multi-thread (en utilisant 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; 
} 

J'ai utilisé deux tableaux pour garder la trace des processus enfants de pid et la zone de pile adresse de base (pour le libérer).
Lorsque j'exécute ce programme, il se termine après un certain temps. Exécuter avec gdb m'indique que l'un des thread obtient un SIGSEGV (défaut de segmentation). Mais cela ne me donne tout lieu, la sortie est similaire à ce qui suit:

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

Je l'ai essayée en cours d'exécution sous valgrind avec la ligne de commande suivante:

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

Mais il continue à fonctionner sans aucun problème sous valgrind.
Je suis perplexe quant à la façon de déboguer ce programme. J'ai senti que cela pourrait être un débordement de pile ou quelque chose mais augmenter la taille de la pile (jusqu'à 74KB) n'a pas résolu le problème.
Ma seule requête est pourquoi et où est l'erreur de segmentation ou comment déboguer ce programme.

+0

pour être honnête, je suis ignorant de la fonction clone, mais je l'ai vu dans OpenMP. Avez-vous essayé de changer la limite de taille de la pile, ulimit -s – Anycorn

Répondre

1

Je pense avoir trouvé la réponse

Étape 1

Remplacer ceci:

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

Par ceci:

static int *thread_pids; 
static void **thread_stacks; 

Étape 2

Ajouter ce dans la fonction principale (après vérification des arguments):

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

Étape 3

Remplacer ceci:

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

Par ceci:

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

Dans les deux endroits, vous l'utilisez.

Je ne sais pas si c'est vraiment votre problème, mais après l'avoir testé, je n'ai pas eu de problème de segmentation. Btw j'ai seulement obtenu des fautes de segmentation en utilisant plus de 5 threads.

J'espère que j'ai aidé!

modifier: testé avec 1000 fils et fonctionne parfaitement

Edit2: Explication pourquoi l'allocation statique de thread_pids et thread_stacks provoque une erreur.

La meilleure façon de faire cela est avec un exemple.

Supposons num_threads = 10;

Le problème se produit dans le code suivant:

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

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

... 
} 

vous essayez ici pour accéder à la mémoire qui ne vous appartient pas (0 < = i < = 9, mais les deux tableaux ont une taille de 5). Cela peut provoquer une erreur de segmentation ou une corruption de données. La corruption de données peut se produire si les deux tableaux sont alloués les uns après les autres, ce qui entraîne l'écriture dans l'autre tableau. La segmentation peut se produire si vous écrivez en mémoire que vous n'avez pas allouée (statiquement ou dynamiquement).

Vous pouvez avoir de la chance et n'avoir aucune erreur, mais le code n'est sûrement pas sûr. A propos du pointeur non-aligné: Je pense que je ne dois pas expliquer plus que dans mon commentaire.

+0

salut George. J'ai essayé le code original sur amd64 et il n'y a pas de segmentation. Je suis curieux de savoir quel est le problème de vos systèmes. Pouvez-vous expliquer brièvement? merci – Anycorn

+0

Salut George, votre solution n'a pas fonctionné pour moi :(BTW, pouvez-vous expliquer pourquoi ces changements ont fonctionné pour vous? – Sukanto

+0

Eh bien j'ai remarqué que je n'ai pas eu de problème avec <= 5 discussions, mais avec plus de 5 threads j'ai toujours eu une erreur de segmentation L'erreur est que vous n'avez pas assez de mémoire alloué pour chaque thread (vous l'allouez de manière statique) avec le résultat de l'accès plus tard dans votre code aux adresses non allouées En soustrayant 1 de chld_stack + 148 * 512 vous obtenez une adresse invalide (doit être aligné, n'est pas parce que l'adresse est un nombre impair) .Cela peut aussi causer une erreur de segmentation ou une corruption de pile Je ne sais pas si vous êtes malchanceux ou moi chanceux , mais comme je l'ai dit, ça a bien marché pour moi – George

3

J'ai trouvé le numéro actuel.
Lorsque le thread de travail signale le processus parent à l'aide de sigqueue(), le parent obtient parfois le contrôle immédiatement et libère la pile avant que l'enfant n'exécute l'instruction return. Lorsque le même thread enfant utilise l'instruction return, il provoque une erreur de segmentation lorsque la pile est corrompue.
Pour résoudre cela, je remplacé

exit(0) 

au lieu de

return 0;