1. Regola fondamentale delle pipe

Una pipe ha:

fd[0] -> lettura
fd[1] -> scrittura

2. Dopo la fork() ogni processo eredita TUTTI i file descriptor

Questa è la cosa più importante da ricordare.

Dopo:

fork();

padre e figlio possiedono:

stdin
stdout
stderr
TUTTE le pipe aperte

Quindi:

bisogna chiudere SEMPRE i descriptor inutili

3. Regola pratica pipe

Processo che scrive

Deve chiudere:

close(fd[0]);

perché non legge.

Processo che legge

Deve chiudere:

close(fd[1]);

perché non scrive.

4. Dopo dup2() chiudi SEMPRE il descriptor originale

Esempio:

dup2(fd[1], STDOUT_FILENO);
close(fd[1]);

Perché dup2() crea una copia del descriptor. Se non chiudi:

  • leak di descriptor
  • EOF non arriva
  • deadlock possibili

5. EOF sulla pipe arriva SOLO se tutti gli writer sono chiusi

Questa è LA regola critica. La read() termina quando:

TUTTI i lati scrittura della pipe sono chiusi

Se anche UN SOLO processo mantiene aperto:

fd[1]

la read() può bloccarsi per sempre.

6. Ogni figlio deve chiudere ANCHE le pipe degli altri figli

Esempio:

pipe(p1);
pipe(p2);
pipe(p3);

Nel figlio associato a p1:

close(p2[0]);
close(p2[1]);
 
close(p3[0]);
close(p3[1]);

sempre.


7. Le pipe non si riusano dopo essere state chiuse

Questa è una pipe:

pipe(fd);

vive per:

creazione -> uso -> chiusura

NON:

creazione -> chiusura -> riuso

Se fai cicli:

while(1)

ricrea le pipe ogni iterazione.


8. Dopo fork() controlla SEMPRE il ritorno

Pattern corretto:

pid_t pid = fork();
 
if(pid < 0)
{
    perror("fork");
    exit(1);
}
 
if(pid == 0)
{
    // figlio
}
else
{
    // padre
}

9. Dopo exec() il codice successivo NON dovrebbe eseguire

Se exec() funziona:

il processo viene sostituito

quindi:

execlp(...);
 
perror("exec");
exit(1);

10. wait() / waitpid() sempre

Ogni figlio terminato senza wait():

diventa zombie

Quindi:

waitpid(pid, NULL, 0);

oppure:

while(wait(NULL) > 0);

11. Mai fare busy waiting

MALE:

while(1);

CPU al 100%.

Meglio:

pause();

oppure:

sleep(1);

12. Nei signal handler usare SOLO funzioni async-signal-safe

Dentro:

void handler(int sig)

NON usare:

  • printf

  • malloc

  • fork

  • execlp

  • ctime

Pattern corretto:

volatile sig_atomic_t flag = 0;
 
void handler(int sig)
{
    flag = 1;
}

13. Pattern classico pipe + exec

Schema standard:

pipe(fd);
 
pid = fork();
 
if(pid == 0)
{
    close(fd[0]);
 
    dup2(fd[1], STDOUT_FILENO);
 
    close(fd[1]);
 
    execlp(...);
 
    exit(1);
}
else
{
    close(fd[1]);
 
    while(read(fd[0], ...) > 0)
    {
        ...
    }
 
    close(fd[0]);
 
    wait(NULL);
}

14. Regola mentale più importante

Quando qualcosa si blocca con le pipe, chiediti sempre:

Chi sta ancora tenendo aperto il lato scrittura?

Nel 90% dei casi il problema è lì.