5

Escribí un código para crear algunos subprocesos y cada vez que uno de los subprocesos termina, se crea un nuevo subproceso para reemplazarlo. Como no pude crear un gran número de subprocesos (> 450) usando pthreads, usé clonación de sistema de clonación en su lugar. (Tenga en cuenta que soy consciente de la implicación de tener una gran cantidad de subprocesos, pero este programa solo sirve para estresar el sistema).
Como clone() requiere que el espacio de pila para el subproceso secundario se especifique como parámetro, malloco el trozo requerido de espacio de pila para cada subproceso y lo libera cuando termina el subproceso. Cuando termina un hilo, le envío una señal al padre para notificarle lo mismo.
El código es la siguiente:Error de segmentación de depuración en un programa de subprocesos múltiples (mediante clonación)

#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; 
} 

he utilizado par de matrices para realizar un seguimiento de la dirección base pid y la zona de pila los procesos hijos (para liberarla).
Cuando ejecuto este programa, termina después de algún tiempo. Correr con gdb me dice que uno de los hilos obtiene un SIGSEGV (fallo de segmentación). Pero no me da ninguna ubicación, la salida es similar al siguiente:

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

Intenté funcionar bajo valgrind con la siguiente orden:

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

pero sigue funcionando sin ningún problema bajo valgrind.
Estoy desconcertado sobre cómo depurar este programa. Sentí que esto podría ser un desbordamiento de pila o algo así, pero aumentar el tamaño de la pila (hasta 74 KB) no resolvió el problema.
Mi única consulta es por qué y dónde está la falla de segmentación o cómo depurar este programa.

+0

para ser honesto, soy ignorante acerca función de clonación, pero he visto esto en OpenMP. ¿Has intentado cambiar el límite de tamaño de la pila, ulimit -s – Anycorn

Respuesta

1

Creo que he encontrado la respuesta

Paso 1

Reemplazar esta:

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

Por esto:

static int *thread_pids; 
static void **thread_stacks; 

Paso 2

Añadir este en la función main (después de comprobar argumentos):

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

Paso 3

Reemplazar esta:

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

Por esto:

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

en ambos lugares lo usa.

No sé si realmente es su problema, pero después de probarlo no tuve ningún error de segmentación. Por cierto, solo obtuve fallas de segmentación cuando uso más de 5 hilos.

Espero que haya ayudado!

edit: probado con 1000 hilos y funciona perfectamente

Edit2: explicación de por qué la asignación estática de thread_pids y thread_stacks produce un error.

La mejor manera de hacerlo es con un ejemplo.

Supongamos num_threads = 10;

El problema se produce en el siguiente código:

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

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

... 
} 

Aquí se intenta acceder a la memoria que no pertenece a usted (0 < = i < = 9, pero ambas matrices tienen un tamaño de 5). Eso puede causar una falla de segmentación o corrupción de datos. La corrupción de datos puede ocurrir si ambas matrices se asignan una detrás de la otra, lo que da como resultado la escritura en la otra matriz. La segmentación puede ocurrir si escribe en la memoria que no ha asignado (estática o dinámicamente).

Puede que tenga suerte y no tenga ningún error, pero seguramente el código no es seguro.

Acerca del puntero no alineado: Creo que no tengo que explicar más que en mi comentario.

+0

Hola George. He probado el código original en amd64 y no obtiene segmentación. Tengo curiosidad sobre cuál es el problema de tus sistemas. ¿Puedes explicar brevemente? gracias – Anycorn

+0

Hola George, tu solución no funcionó para mí :(Por cierto, ¿puedes explicar por qué esos cambios funcionaron para ti? – Sukanto

+0

Bueno, noté que no tuve ningún problema con <= 5 hilos, pero con más de 5 threads siempre recibí una falla de segmentación El error es que no tienes suficiente memoria asignada para cada hilo (lo asignas estáticamente) con el resultado de acceder a direcciones no asignadas más adelante en tu código También restando 1 de chld_stack + 148 * 512 obtienes una dirección inválida (debe estar alineada con la palabra, no es porque la dirección es un número impar). Eso también puede causar una falla de segmentación o una corrupción de la pila. No sé si tienes mala suerte o soy afortunado , pero como dije, funcionó bien para mí. – George

3

Encontró el problema real.
Cuando el subproceso de trabajo señala el proceso principal utilizando sigqueue(), el elemento primario a veces obtiene el control inmediatamente y libera la pila antes de que el hijo ejecute la instrucción return. Cuando el mismo subproceso secundario usa instrucción return, causa un error de segmentación a medida que la pila se corrompe.
Para resolver este Substituí

exit(0) 

en lugar de

return 0; 
Cuestiones relacionadas