2012-07-22 12 views
5

Dato il seguente codice:Cosa succede se un processo figlio non chiude la pipe dalla scrittura, durante la lettura?

int main(int argc, char *argv[]) 
{ 
    int pipefd[2]; 
    pid_t cpid; 
    char buf; 

    if (argc != 2) { 
     fprintf(stderr, "Usage: %s \n", argv[0]); 
     exit(EXIT_FAILURE); 
    } 

    if (pipe(pipefd) == -1) { 
     perror("pipe"); 
     exit(EXIT_FAILURE); 
    } 

    cpid = fork(); 
    if (cpid == -1) { 
     perror("fork"); 
     exit(EXIT_FAILURE); 
    } 

    if (cpid == 0) { /* Child reads from pipe */ 
     close(pipefd[1]);   /* Close unused write end */ 

     while (read(pipefd[0], &buf, 1) > 0) 
      write(STDOUT_FILENO, &buf, 1); 

     write(STDOUT_FILENO, "\n", 1); 
     close(pipefd[0]); 
     _exit(EXIT_SUCCESS); 

    } else {   /* Parent writes argv[1] to pipe */ 
     close(pipefd[0]);   /* Close unused read end */ 
     write(pipefd[1], argv[1], strlen(argv[1])); 
     close(pipefd[1]);   /* Reader will see EOF */ 
     wait(NULL);    /* Wait for child */ 
     exit(EXIT_SUCCESS); 
    } 
return 0; 

} 

Ogni volta che il processo figlio vuole leggere dal tubo, si deve prima chiudere lato del tubo dalla scrittura. Quando rimuovo la riga close(pipefd[1]); dal processo figlio if, , sto praticamente dicendo che "okay, il bambino può leggere dalla pipe, ma sto permettendo al genitore di scrivere sulla pipe allo stesso tempo"?

In tal caso, cosa accadrebbe quando la pipe è aperta sia per la lettura della scrittura &? Nessuna esclusione reciproca?

+0

Leggere() e scrivere() su un tubo sono garantiti come atomici. (fino alla dimensione PIPE_BUFF). Ciò significa: primo arrivato, primo servito. Le parti scritte/lette saranno interlacciate, ma i loro confini sono ancora intatti. – wildplasser

+2

Se non si chiude la fine di scrittura del child della pipe, il bambino non vedrà mai un EOF, poiché EOF verrà visualizzato solo quando la fine della scrittura è stata chiusa da tutti i suoi utenti. Vedi http://stackoverflow.com/questions/7868018/last-child-forked-will-not-die per un esempio. – ninjalj

risposta

13

Ogni volta che il processo figlio vuole leggere dal tubo, si deve prima chiudere lato del tubo dalla scrittura.

Se il processo - padre o figlio - non utilizzerà la fine della scrittura di una pipe, dovrebbe chiudere il descrittore di file. Allo stesso modo per l'estremità letta di un tubo. Il sistema supporrà che possa verificarsi una scrittura mentre qualsiasi processo ha la fine della scrittura aperta, anche se l'unico processo è quello che sta attualmente cercando di leggere dalla pipe, e quindi il sistema non segnalerà EOF. Inoltre, se si riempie eccessivamente una pipe e c'è ancora un processo con la fine di lettura aperta (anche se quel processo è quello che tenta di scrivere), allora la scrittura si bloccherà, in attesa che il lettore faccia spazio per il completamento della scrittura.

Quando rimuovo quella linea chiusa (pipefd [1]); dal processo del bambino SE, sostanzialmente sto dicendo che "okay, il bambino può leggere dal tubo, ma sto permettendo al genitore di scrivere sul tubo allo stesso tempo"?

No; stai dicendo che il bambino può scrivere alla pipe e al genitore. Qualsiasi processo con il descrittore di file di scrittura per la pipe può scrivere nella pipe.

In tal caso, che cosa accadrebbe quando la conduttura è aperta sia per la lettura che per la scrittura - nessuna esclusione reciproca?

Non vi è alcuna esclusione reciproca in assoluto. Qualsiasi processo con il descrittore di scrittura del tubo aperto può scrivere sul tubo in qualsiasi momento; il kernel assicura che due operazioni di scrittura simultanee siano di fatto serializzate. Qualsiasi processo con il descrittore di lettura del tubo aperto può leggere dal tubo in qualsiasi momento; il kernel garantisce che due operazioni di lettura simultanee ottengano byte di dati diversi.

Assicurati che un tubo venga utilizzato unidirezionalmente assicurandosi che solo un processo lo apra per la scrittura e solo un processo lo apra per la lettura. Tuttavia, questa è una decisione di programmazione. Potresti avere N processi con la fine di scrittura aperta e M processi con la fine di lettura aperta (e, perire il pensiero, potrebbero esserci processi in comune tra l'insieme di N e l'insieme di processi M) e sarebbero tutti in grado lavorare sorprendentemente bene. Ma non saresti prontamente in grado di prevedere dove un pacchetto di dati verrebbe letto dopo che è stato scritto.

+1

Ottima risposta! +100 se potessi! – ron

3

fork() duplica gli handle del file, quindi avrai due maniglie per ciascuna estremità del tubo.

Ora, considera questo. Se il genitore non chiude l'estremità inutilizzata della pipa, ci saranno ancora due maniglie per esso. Se il bambino muore, la maniglia sul lato bambino scompare, ma c'è ancora il manico aperto tenuto dal genitore - quindi, non ci sarà mai un "tubo rotto" o "EOF" in arrivo perché la pipa è ancora perfettamente valida. Non c'è nessuno che ci metta i dati dentro.

Lo stesso vale per l'altra direzione, ovviamente.

Sì, il genitore/figlio può ancora utilizzare l'handle per scrivere nella propria pipe; Non ricordo un caso d'uso per questo, però, e ti dà ancora problemi di sincronizzazione.

1

Quando si crea il tubo, si hanno due estremità leggere e scrivere fine. Queste sono voci nella tabella descrittore file utente.

Analogamente ci saranno due voci nella tabella File con 1 come conteggio di riferimento sia per la fine della lettura che per la fine della scrittura.

Ora, quando si forchetta, un bambino è stato creato, che è il file descrittori vengono duplicati e quindi il conteggio di riferimento di entrambe le estremità della tabella file diventa 2.

Now "Quando rimuovo quella linea stretta (pipefd [1]) "-> In questo caso, anche se il genitore ha completato la scrittura, il tuo ciclo while sotto questa linea bloccherà per sempre affinché la lettura ritorni a 0 (cioè EOF). Ciò accade poiché anche se il genitore ha completato la scrittura e chiuso la fine della scrittura della pipe, il conteggio dei riferimenti della fine della scrittura nella tabella File è ancora 1 (Inizialmente era 2) e quindi la funzione di lettura è ancora in attesa di alcuni dati arrivare che non accadrà mai.

Ora se non è stato scritto "close (pipefd [0]);" nel genitore, questo codice attuale potrebbe non mostrare alcun problema, dal momento che si sta scrivendo una volta nel genitore.

Ma se scrivi più di una volta allora idealmente avresti voluto ottenere un errore (se il bambino non sta leggendo più), ma dal momento che la fine di lettura nel genitore non è chiusa, non otterrai l'errore (Anche se il bambino non c'è più da leggere).

Quindi il problema di non chiudere le estremità inutilizzate diventa evidente quando si legge/scrive continuamente dati. Questo potrebbe non essere evidente se stiamo solo leggendo/scrivendo i dati una volta.

Come se al posto del ciclo di lettura nel bambino, si utilizzi solo una volta la riga sottostante, dove si ottengono tutti i dati in una volta sola, e senza preoccuparsi di controllare EOF, il programma funzionerà anche se si non stanno scrivendo "close (pipefd [1]);" nel bambino.

read(pipefd[0], buf, sizeof(buf));//buf is a character array sufficiently large 
+0

Fai attenzione alla distinzione tra [apri le descrizioni dei file] (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_253) e [apri descrittori di file] (http: //pubs.opengroup. org/onlinepubs/9699919799/basedefs/V1_chap03.html # tag_03_166). È davvero un duro lavoro essere precisi. Il conteggio è nella descrizione del file aperto, non nel descrittore. –

+0

@ JonathanLeffler Grazie Jonathan."Voci tabella file" che ho menzionato nella risposta e "descrizioni di file aperti" sperano che si riferiscano alla stessa cosa. –

1

pagina man per il tubo() per SunOS: - Leggi invita un tubo vuoto (non ci sono dati memorizzati nel buffer) con un solo fine (tutti i descrittori di file di scrittura chiusi) restituirà un EOF (fine del file).

A SIGPIPE signal is generated if a write on a pipe with only 
one end is attempted. 
Problemi correlati