PDA

Visualizza la versione completa : [C] blocco pee generazione di troppi figli


davidinho
07-07-2013, 22:36
Scusate se questi giorni posto molte discussioni, ma voglio cercare di capire il tutto bene...
Ho quest'altro programmino che genera n figli, con n passato come argomento da terminale(non badate alla non presenza dei controlli sugli argomenti passati), funziona correttamente fino a 3 figli, oltre da problemi strani, a volte si blocca in attesa di non si sa cosa, altre volte da un errore lunghissimo che riporto in fondo al post

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "signal.h"

int sigCounter=0; //contatore che indica il numero di figli attualmente "in circolazione"

void generaFiglio(int n)
{//sleep(1); //se inserisco questa sleep il programma gira correttamente
int pid=(int)fork();
sigCounter++;
if(pid==0)
{
exit(n);
}
if(pid<0)
{
sigCounter--;
perror("fork error");
exit(1);
}
}

void catturaSegnale(int num)
{
printf("%d ha ricevuto il segnale SIGCHLD(%d)\n", (int)getpid(), num);
sigCounter--;
}


int main(int argc, char **argv)
{
int status;
int nFigli=atoi(argv[1]); // numero di figli da generare preso da terminale
signal(SIGCHLD, catturaSegnale);
int i;
int pid;
for(i=0; i<nFigli; i++) // genero nFigli
generaFiglio(i);

while(sigCounter>0); //attendo che tutti i SIGCHLD siano stati ricevuti
printf("Tutti i segnali SIGCHLD ricevuti!\n");

for(i=0; i<nFigli; i++) //stampo il PID e l'exit status di tutti i figli
{
pid=(int)wait(&status);
printf("PID=%d - Exit status=%d\n", pid, status);
}

exit(0);
}

quest'errore appare mentre sta ancora ricevendo i vari SIGCHLD

es2: ../nptl/sysdeps/unix/sysv/linux/x86_64/../fork.c:211: __libc_fork: Assertion `({ __typeof (({ struct pthread *__self; asm ("mov %%fs:%c1,%0" : "=r" (__self) : "i" (__builtin_offsetof (struct pthread, header.self))); __self;})->tid) __value; if (sizeof (__value) == 1) asm volatile ("movb %%fs:%P2,%b0" : "=q" (__value) : "0" (0), "i" (__builtin_offsetof (struct pthread, tid))); else if (sizeof (__value) == 4) asm volatile ("movl %%fs:%P1,%0" : "=r" (__value) : "i" (__builtin_offsetof (struct pthread, tid))); else { if (sizeof (__value) != 8) abort (); asm volatile ("movq %%fs:%P1,%q0" : "=r" (__value) : "i" (__builtin_offsetof (struct pthread, tid))); } __value; }) == ppid' failed.
Annullato (core dump creato)(es2 è il nome del programma)


da cosa può dipendere questo problema? ho visto che aggiungendo una sleep(1) in generaFiglio il programma gira anche passando 10, quindi sembra quasi un problema di velocità :dhò:

MItaly
08-07-2013, 01:41
Ci sono due errori fondamentali:

la printf nel signal handler; nei gestori di segnali le uniche funzioni "sicure" sono quelle elencate nell'apposita sezione in man 7 signal; printf (e in generale tutte le funzioni di IO C) non è tra queste. Da questo dipende l'"errore strano" che ottieni.
In generale, in un signal handler asincrono bisognerebbe fare il meno possibile, dato che questo viene eseguito su uno stack momentaneamente "preso a prestito", interrompendo a metà l'esecuzione di altro codice.
il fatto che tu ti aspetti di ricevere tanti SIGCHLD quanti processi hai creato; se più SIGCHLD arrivano prima che il signal handler sia effettivamente chiamato, questo viene comunque chiamato una sola volta, e se un SIGCHLD arriva mentre sei dentro al signal handler possono succedere cose diverse a seconda del sistema operativo (motivo per cui la signal() è sostanzialmente deprecata in favore della sigaction, che definisce in maniera chiara cosa accade in questo caso).
In ogni caso, sia con signal che con sigaction non ti puoi attendere di ottenere il numero esatto di SIGCHLD, ed è il motivo per cui il tuo programma si blocca: sigCounter non va mai a zero, per cui il programma rimane bloccato nel while.

MItaly
08-07-2013, 01:49
PS: tutti gli header che hai incluso vanno inclusi con le parentesi angolari (#include <unistd.h>, #include <stdio.h>, ...), non con le virgolette. Le virgolette sono per gli header specifici di questo progetto (per cui i file in questione vengono prima cercati nella directory corrente), mentre le parentesi angolari sono per gli header di sistema (come quelli specificati in questo caso).

davidinho
08-07-2013, 01:59
Originariamente inviato da MItaly
Ci sono due errori fondamentali:

la printf nel signal handler; nei gestori di segnali le uniche funzioni "sicure" sono quelle elencate nell'apposita sezione in man 7 signal; printf (e in generale tutte le funzioni di IO C) non è tra queste. Da questo dipende l'"errore strano" che ottieni. In generale, in un signal handler asincrono bisognerebbe fare il meno possibile, dato che questo viene eseguito su uno stack momentaneamente "preso a prestito", interrompendo a metà l'esecuzione di altro codice.
il fatto che tu ti aspetti di ricevere tanti SIGCHLD quanti processi hai creato; se più SIGCHLD arrivano prima che il signal handler sia effettivamente chiamato, questo viene comunque chiamato una sola volta, e se un SIGCHLD arriva mentre sei dentro al signal handler possono succedere cose diverse a seconda del sistema operativo (motivo per cui la signal() è sostanzialmente deprecata in favore della sigaction, che definisce in maniera chiara cosa accade in questo caso).
si impara più da un forum che da un professore universitario :facepalm:
Comunque quindi per stampare a schermo leggendo il man vedo che si può usare write(), invece per attendere che tutti i signal siano stati inviati è impossibile finchè si usa signal()? o c'è comunque un metodo?
purtroppo devo adattarmi ad usare signal() e non posso passare alla sigaction perché l'esame richiede la prima(poi comunque dopo l'esame di sicuro mi vedrò la seconda)...

MItaly
08-07-2013, 02:08
Originariamente inviato da davidinho
invece per attendere che tutti i signal siano stati inviati è impossibile finchè si usa signal()? o c'è comunque un metodo?
Sposta prima il ciclo con le wait - dopo le wait sei sicuro che tutti i SIGCHLD sono stati consegnati.
Tra l'altro, quel ciclo non tiene conto dell'eventualità che la fork possa essere fallita - in tal caso ti ritrovi il processo bloccato nella wait. Per essere davvero sicuro dovresti modificare il ciclo perché attenda solo tanti child quanti ne sono stati fatti effettivamente partire.

davidinho
08-07-2013, 02:41
ho aggiunto una variabile globale nEFigli(e sta per effettivo) che si incrementa per ogni fork e in caso di fallimento viene ridiminuito(come il sigCounter) quindi ora non dovrebbero esserci problemi

Comunque usando solo le wait() quindi è sicuro che non ci siano problemi, ma se io volessi per ogni signal ricevuto stampare a schermo "Signal ricevuto" o un qualcosa di simile non potrò mai essere sicuro che tutto vada bene?
non è una cosa che interessa a me in particolare per qualcosa, ma ho visto che in alcuni testi d'esame veniva chiesta una cosa simile del tipo "Quando riceve il segnale SIGUSR1, il signal handler deve stampare su stdout il messaggio "SIGUSR1 ricevuto". "

MItaly
08-07-2013, 03:48
Usa la write. printf & co. assolutamente no, dato che non sono rientranti. Come detto, sopra, in generale le uniche funzioni di libreria consentite in un gestore di signal asincrono sono quelle elencate in man 7 signal (link (http://linux.die.net/man/7/signal)), sotto "Async-signal-safe functions".

davidinho
08-07-2013, 11:29
no scusami, con "se io volessi per ogni signal ricevuto stampare a schermo "Signal ricevuto" o un qualcosa di simile non potrò mai essere sicuro che tutto vada bene?" non intendo per la questione del printf, infatti per quello ho già risolto con write, dicevo per la questione che più segnali potrebbero arrivare consecutivamente ed essere presi in consegna da un'unico signal handler diminuendo cosi il sigCounter una sola volta... usando signal() non è risolvibile? invece sigaction risolverebbe questo problema?

MItaly
08-07-2013, 13:09
Per quanto ne so io non si può risolvere; o meglio, sigaction ti dà una semantica definita per quello che accade se arriva un signal mentre sei in un signal handler, ma comunque da quel che ne so c'è sempre la possibilità di "perdere" segnali per strada se arrivano due segnali prima che il signal handler abbia l'occasione di essere eseguito.

In realtà comunque è possibile sapere quanti figli sono terminati nel signal handler: basta richiamare la waitpid in un ciclo specificando l'opzione WNOHANG, e contare quante volte "gira" il ciclo prima che la waitpid restituisca un errore.

Tutte queste bizzarrie nella gestione dei segnali derivano dal fatto che sono un po' un sistema del cavolo, nato in un'altra epoca su macchine decisamente meno potenti (e senza supporto nativo al multithreading); correntemente infatti è molto più sensato usare, ove possibile, meccanismi di notifica più moderni (ad esempio, in determinati casi è possibile ottenere una notifica in un thread separato).

Loading