C e segnali

Forum dedicato alla programmazione.

Moderatore: Staff

Regole del forum
1) Citare in modo preciso il linguaggio di programmazione usato.
2) Se possibile portare un esempio del risultato atteso.
3) Leggere attentamente le risposte ricevute.
4) Scrivere i messaggi con il colore di default, evitare altri colori.
5) Scrivere in Italiano o in Inglese, se possibile grammaticalmente corretto, evitate stili di scrittura poco chiari, quindi nessuna abbreviazione tipo telegramma o scrittura stile SMS o CHAT.
6) Appena registrati è consigliato presentarsi nel forum dedicato.

La non osservanza delle regole porta a provvedimenti di vari tipo da parte dello staff, in particolare la non osservanza della regola 5 porta alla cancellazione del post e alla segnalazione dell'utente. In caso di recidività l'utente rischia il ban temporaneo.
Avatar utente
targzeta
Iper Master
Iper Master
Messaggi: 6631
Iscritto il: gio 3 nov 2005, 14:05
Nome Cognome: Emanuele Tomasi
Slackware: 64-current
Kernel: latest stable
Desktop: IceWM
Località: Carpignano Sal. (LE) <-> Pisa

Re: C e segnali

Messaggio da targzeta »

Mario Vanoni ha scritto:...
Scusa l'intrusione, ma dove trovo una documentazione su wait_child?...
Mario, ma l'hai letto il suo codice? wait_child() è una funzione che ha definito lui.

Emanuele
Se pensi di essere troppo piccolo per fare la differenza, prova a dormire con una zanzara -- Dalai Lama

Mario Vanoni
Iper Master
Iper Master
Messaggi: 3174
Iscritto il: lun 3 set 2007, 21:20
Nome Cognome: Mario Vanoni
Slackware: 12.2
Kernel: 3.0.4 statico
Desktop: fluxbox/seamonkey
Località: Cuasso al Monte (VA)

Re: C e segnali

Messaggio da Mario Vanoni »

spina ha scritto:
Mario Vanoni ha scritto:...
Scusa l'intrusione, ma dove trovo una documentazione su wait_child?...
Mario, ma l'hai letto il suo codice? wait_child() è una funzione che ha definito lui.

Emanuele
NO, chiedo venia, ma leggendola,
la sua funzione void wait_child() non fa niente oltre ai printf()!
Quindi una NOP instruction inutile.

Avatar utente
targzeta
Iper Master
Iper Master
Messaggi: 6631
Iscritto il: gio 3 nov 2005, 14:05
Nome Cognome: Emanuele Tomasi
Slackware: 64-current
Kernel: latest stable
Desktop: IceWM
Località: Carpignano Sal. (LE) <-> Pisa

Re: C e segnali

Messaggio da targzeta »

@Riccardo
Peccato che questo corso sia finito così, ma cosa vi insegnano!!! Comunque, nota che nella soluzione c'è una sleep(1) del figlio 2, per com'è posto l'esercizio questo rallenta tutto e quindi il codice risulta affidabile, infatti il figlio 1 riceve segnali dal figlio 2 con distanza di un secondo l'uno dall'altro e lo stesso accade al padre. Si presume che in un secondo di tempo il signal handler abbia finito di fare quello che deve.
Per inciso, anche nella nostra soluzione se ci metti uno sleep(1) dopo l'invio del segnale tutto funziona bene. Inoltre hai notato che nelle problematiche c'è l'ipotesi di assumere il modello a segnali affidabile?

Secondo me a questo punto possiamo dire che i segnali vengono persi, anche se durante la gestione dell'handler il segnale è bloccato e pendente, più segnali potrebbero essere inviati come uno singolo.

Emanuele

:edit: detto tra noi, non è che la soluzione sia bellissima :D.
:edit2: anche mettendo una nanosleep(2) di 1 nanosecondo la mia soluzione funziona bene.
Se pensi di essere troppo piccolo per fare la differenza, prova a dormire con una zanzara -- Dalai Lama

Avatar utente
targzeta
Iper Master
Iper Master
Messaggi: 6631
Iscritto il: gio 3 nov 2005, 14:05
Nome Cognome: Emanuele Tomasi
Slackware: 64-current
Kernel: latest stable
Desktop: IceWM
Località: Carpignano Sal. (LE) <-> Pisa

Re: C e segnali

Messaggio da targzeta »

Mario Vanoni ha scritto:...
NO, chiedo venia, ma leggendola,
la sua funzione void wait_child() non fa niente oltre ai printf()!
Quindi una NOP instruction inutile.
Ma che dici Mario, la funzione fa si che il padre aspetti la terminazione dei figli e stampa in output una stringa che spiega se il figlio è terminato regolarmente. Scema o no la stampa, la wait() andrebbe comunque chiamata IMHO, tra l'altro se guardi il mio codice io ho eliminato il while() più il pause() proprio perchè preferisco aspettare la terminazione dei figli con la wait() per i motivi che ho spiegato in un altro post.

Emanuele
Se pensi di essere troppo piccolo per fare la differenza, prova a dormire con una zanzara -- Dalai Lama

Avatar utente
targzeta
Iper Master
Iper Master
Messaggi: 6631
Iscritto il: gio 3 nov 2005, 14:05
Nome Cognome: Emanuele Tomasi
Slackware: 64-current
Kernel: latest stable
Desktop: IceWM
Località: Carpignano Sal. (LE) <-> Pisa

Re: C e segnali

Messaggio da targzeta »

Allora Riccardo, ho studiato un po' :).
  • I segnali POSIX attuali sono detti "affidabili" (reliable). Questo implica semplicemente il fatto che quando un handler è in esecuzione il segnale viene bloccato e quindi successivi segnali rimangono pendenti
  • Se vengono spediti 100 segnali dello stesso tipo prima che il processo li gestisce, allora esso ne riceve sempre e solo uno. In altre parole questi segnali (i segnali reliable) non sono accumulabili
  • Non c'è un ordine di consegna, NON si fa chi prima arriva meglio alloggia
Quindi il problema nel nostro codice è relativo al fatto che un figlio invia un sacco di segnali mentre il padre ne riceve molti meno. Infatti supponi che in questo momento sia in esecuzione il figlio, se lui riesce a controllare 100 righe prima che venga schedulato e se 80 di queste righe hanno più di N caratteri allora lui invierà 80 segnali al padre, quando questo però viene portato in esecuzione gli verrà segnalata la presenza del segnale, ma di uno solo.

Per superare questi limiti esistono i segnali POSIX real-time. Per questi vale quanto segue:
  • Sono accumulabili
  • Hanno un ordine di consegna
  • Trasportano informazione, laddove generalmente i segnali per loro natura non lo fanno
In conclusione, se non si mette una pausa tra l'invio di un segnale e il prossimo allora i segnali potrebbero essere persi. Quanta dev'essere questa pausa? Non vale la pena chiederselo perchè semplicemente questo implica che è molto difficile fare IPC con i segnali, almeno per gli esercizi proposti.

Io ho provato a fare un protocollo di comunicazione in cui il figlio manda un segnale al padre e poi aspetta un segnale dal padre in questo modo: per il figlio

Codice: Seleziona tutto

...
kill(getppid(), SIGUSR1);
raise(SIGSTOP);
...
mentre per il padre, nell'handler

Codice: Seleziona tutto

...
kill(child_pid, SIGCONT);
...
Però anche qui supponiamo il seguente scenario (vedi P.P.S.): il figlio manda un segnale al padre e viene schedulato; quindi il padre gestisce il segnale e fa un SIGCONT al figlio; ora il figlio prima esegue il codice associato a SIGCONT (nulla) e poi fa una raise(SIGSTOP) che lo blocca in eterno.
L'unica alternativa sarebbe settare un allarme che sveglia comunque il figlio, ma questo complicherebbe le cose perchè si dovrebbero installare altri due handler, uno per il SIGALRM e l'altro per il SIGCONT, il secondo deve segnalare che è stato ricevuto un SIGCONT dal padre, mentre il primo deve controllare se il padre ha spedito il SIGCONT ed in caso negativo rimettere un allarme e riportare il figlio in pausa con la raise(SIGSTOP).

Spero di averti chiarito un po' di più le idee,
Emanuele

P.S. Comunque tutto ciò era scritto in signal(7) che ti avevo consigliato di leggere [-( :D
P.P.S. Ecco un esempio di deadlock per il protocollo proposto. Il formato è "PID: evento":

Codice: Seleziona tutto

...
6054: mando il segnale 12
6054: aspetto
6052: sveglio il figlio 6054
6053: mando il segnale 10
6052: sveglio il figlio 6053
6053: aspetto
...
6052 -> padre
6053 -> figlio1
6054 -> figlio2
Come vedi i primi tre output sono corretti: figlio2 invia segnale al padre; figlio2 aspetta; padre sveglia figlio2.
Poi guarda che succede: figlio1 manda il segnale al padre e subito dopo viene schedulato; il padre gestisce il segnale e sveglia il figlio che però non si era ancora messo in attesa; ora il figlio1 si mette in attesa infinita.
Se pensi di essere troppo piccolo per fare la differenza, prova a dormire con una zanzara -- Dalai Lama

Avatar utente
targzeta
Iper Master
Iper Master
Messaggi: 6631
Iscritto il: gio 3 nov 2005, 14:05
Nome Cognome: Emanuele Tomasi
Slackware: 64-current
Kernel: latest stable
Desktop: IceWM
Località: Carpignano Sal. (LE) <-> Pisa

Re: C e segnali

Messaggio da targzeta »

Per completezza posto il codice del nostro programma funzionante con il protocollo che ti ho descritto prima usando anche l'allarme:

Codice: Seleziona tutto

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#define NCHILD   2

void print_usage()
{
  printf("Usage: contaCaratteri <c1> <c2> <max> <fileIn> <fileOut>\n");
}

struct child_info
{
  int exceeding_lines,
    pid,
    signal;

  char control_character;
};

struct child_info c_info[NCHILD];
int terminated, wake_up;

void f_child(char *, struct child_info *, int);
void sig_handler(int);

int main(int argc, char ** argv)
{
  FILE *file;
  int i, pid, status, max;

  if ( argc != 6 )
    {
      print_usage();
      return -1;
    }

  max = atoi(argv[3]);
  c_info[0].control_character = *(argv[1]);
  c_info[1].control_character = *(argv[2]);

  /* FIX: l'esistenza del file si fa con la stat() */
  if ( (file = fopen(argv[4], "r")) == NULL )
    {
      printf("Il file in lettura non esiste.\n");
      return -1;
    }
  fclose(file);

  c_info[0].signal = SIGUSR1;
  c_info[1].signal = SIGUSR2;

  for ( i = 0; i < NCHILD; i++ )
    if ( (signal(c_info[i].signal, sig_handler)) == SIG_ERR )
      {
	perror("signal() non riuscita");
	return -1;
      }

  if ( (signal(SIGTERM, sig_handler)) == SIG_ERR )
    {
      perror("signal() non riuscita");
      return -1;
    }

  for ( i = 0; i < NCHILD; i++ )
    if ( (pid = fork()) < 0 )
      {
	perror("Fork error");
	return -1;
      }
    else
      {
	if ( pid == 0 )
	  f_child(argv[4], &c_info[i], max); /* codice per i figli */
	else
	  c_info[i].pid = pid; /* codice per il padre */
      }

  /* Solo il padre */
  while ( terminated != 2 )
    pause();

  for ( i = 0; i < NCHILD; i++ )
    {
      pid = wait(&status);
      if ( WIFEXITED(status) )
        printf("Il processo figlio con pid %d è terminato volontariamente con stato %d\n", pid, WEXITSTATUS(status));
      else
        if ( WIFSIGNALED(status) )
          printf("Il processo figlio con pid %d è terminato involontariamente a causa del segnale %d\n", pid, WTERMSIG(status));
    }

  /* Scrittura risultato in fileOut */
  if ( (file = fopen(argv[5], "w")) == NULL )
    {
      printf("Errore nell'apertura del file in scrittura.\n");
      return -1;
    }
  for ( i = 0; i < NCHILD; i++ )
    if ( fprintf(file, "Il file contiene %d linee con più di %d occorrenze di %c\n", c_info[i].exceeding_lines, max, c_info[i].control_character) < 0 )
      printf("Errore in scrittura del file.\n");
  fclose(file);

  return 0;
}

/* Funzione per i figli */
void f_child(char *path, struct child_info *c_info, int max)
{
  FILE *file;
  int buff, occurrance, sig_sent, pid;

  if ( (signal(SIGCONT, sig_handler)) == SIG_ERR  || (signal(SIGALRM, sig_handler)) == SIG_ERR )
    {
      perror("signal() non riuscita");
      exit(-1);
    }

  if ( (file = fopen(path, "r")) == NULL )
    {
      kill(getppid(), SIGTERM);
      exit(-1);
    }

  sig_sent = occurrance = 0;
  pid = getpid();
  while ( (buff = fgetc(file)) != EOF )
    if ( buff == '\n' )
      {
        if ( occurrance > max )
	  {
	    wake_up = 0;
	    printf("%d: mando il segnale %d\n", pid, c_info->signal);
	    kill(getppid(), c_info->signal);
	    printf("%d: aspetto\n", pid);
	    alarm(1);
	    raise(SIGALRM);
	    sig_sent++;
	  }
        occurrance = 0;
      }
    else
      if ( buff == c_info->control_character )
        ++occurrance;
  /* FIX: controllare se buff e' veramente EOF o un errore */
  fclose(file);

  printf("%d: TERMINO. Ho inviato %d segnali\n", pid, sig_sent);
  kill(getppid(), SIGTERM);
  exit(0);
}

void sig_handler(int signum)
{
  int id = -1;

  switch ( signum )
    {
      /* Gestiti dal padre */
    case SIGUSR1:
      id = 0;
      break;

    case SIGUSR2:
      id = 1 ;
      break;

    case SIGTERM:
      terminated++;
      break;

      /* Gestiti dai figli */
    case SIGCONT:
      printf("%d: ricevuto segnale padre\n", getpid());
      wake_up = 1;
      break;

    case SIGALRM:
      if ( ! wake_up )
	{
	  printf("%d: aspetto nel sig_handler() \n", getpid());
	  alarm(1);
	  raise(SIGALRM);
	}
      break;
    }

  if ( id != -1 )
    {
      c_info[id].exceeding_lines++;
      printf("%d: sveglio il figlio %d\n", getpid(), c_info[id].pid);
      kill(c_info[id].pid, SIGCONT);
    }

  return;
}
Il più sono info in output. Il succo del codice sta nel protocollo:
  • Figlio manda segnale al padre e quindi si mette in attesa
  • Padre gestisce il segnale ed invia un segnale al figlio per informalo
  • Ora il figlio sa che può spedire un altro messaggio al padre
Nota che:
  • A differenza delle soluzioni con la sleep() tra l'invio di un segnale ed un altro, questa soluzione funziona sempre. Infatti la sleep() non garantisce che il padre abbia correttamente gestito il segnale, questo è vero sui nostro PC ma su una macchina che ha un carico molto pesante di processi non si può sapere a priori per quanto tempo "dormire".
  • L'attesa su SIGALRM è necessaria per garantire la corretta esecuzione del caso: figlio manda messagio al padre; padre avvisa il figlio; figlio gestisce l'avviso del padre e poi si mette in una attesa infinita
Emanuele
Se pensi di essere troppo piccolo per fare la differenza, prova a dormire con una zanzara -- Dalai Lama

Avatar utente
ulisse89
Packager
Packager
Messaggi: 643
Iscritto il: sab 17 gen 2009, 12:53
Nome Cognome: Riccardo
Slackware: 13.0
Kernel: 2.6.29.6
Desktop: Xfce
Località: Bologna

Re: C e segnali

Messaggio da ulisse89 »

Allora Riccardo, ho studiato un po' :).

* I segnali POSIX attuali sono detti "affidabili" (reliable). Questo implica semplicemente il fatto che quando un handler è in esecuzione il segnale viene bloccato e quindi successivi segnali rimangono pendenti
* Se vengono spediti 100 segnali dello stesso tipo prima che il processo li gestisce, allora esso ne riceve sempre e solo uno. In altre parole questi segnali (i segnali reliable) non sono accumulabili
* Non c'è un ordine di consegna, NON si fa chi prima arriva meglio alloggia

Quindi il problema nel nostro codice è relativo al fatto che un figlio invia un sacco di segnali mentre il padre ne riceve molti meno. Infatti supponi che in questo momento sia in esecuzione il figlio, se lui riesce a controllare 100 righe prima che venga schedulato e se 80 di queste righe hanno più di N caratteri allora lui invierà 80 segnali al padre, quando questo però viene portato in esecuzione gli verrà segnalata la presenza del segnale, ma di uno solo.

Per superare questi limiti esistono i segnali POSIX real-time. Per questi vale quanto segue:

* Sono accumulabili
* Hanno un ordine di consegna
* Trasportano informazione, laddove generalmente i segnali per loro natura non lo fanno
Grazie Emanuele.
Avevo dato un occhiata alla signal(7), ma non ho proprio visto la parte della perdita di segnali.
Comunque finalmente mi è chiaro come funziona, e di conseguenza come risolverlo.
In ogni caso, è come dici te, le soluzioni con la sleep, come quelle che ci venivano proposte, non sono per niente affidabili, e non mi erano mai piaciute.
La tua già mi piace di più. Non ho ancora guardato il codice nel dettaglio, ma come idea va bene. Il figlio non prosegue sino a quando non è sicuro che il padre abbia finito l'elaborazione del precedente segnale ricevuto.

Adesso proverò a modificare il mio codice con queste informazioni e poi magari ti aggiornerò.
Intanto grazie ancora di tutto! ;)

Avatar utente
ulisse89
Packager
Packager
Messaggi: 643
Iscritto il: sab 17 gen 2009, 12:53
Nome Cognome: Riccardo
Slackware: 13.0
Kernel: 2.6.29.6
Desktop: Xfce
Località: Bologna

Re: C e segnali

Messaggio da ulisse89 »

Ho anche scoperto il motivo per cui la mia soluzione (del 2° esercizio però) non veniva, ma qui era solo un errore madornale.
Non aprivo il file giusto in scrittura. E' brutto scoprirlo dopo un pomeriggio intero, ma almeno mi ha portato a indagare di più sui segnali.

Rispondi