Ricerca…


introduzione

I tubi sono un meccanismo per la comunicazione interprocessa o unidirezionale all'interno della portata di una singola macchina. Logicamente, una pipe consiste di due terminali connessi, uno a cui i dati possono essere scritti, e un altro da cui possono essere successivamente letti quei dati, con un buffer di dati tra tali che non è richiesto che le scritture e le letture siano sincrone. I tubi dovrebbero essere distinti dalle tubature shell, che sono un'applicazione di tubi.

Creazione e utilizzo di base

Le pipe anonime, o semplicemente pipe, sono oggetti gestiti dal kernel esposti ai processi come una coppia di descrittori di file, uno per il terminale di lettura e uno per il terminale di scrittura. Vengono creati tramite la funzione pipe (2):

int pipefds[2];
int result;

result = pipe(pipefds);

In caso di successo, pipe() registra il descrittore per la fine di lettura della pipe all'indice 0 dell'array fornito e il descrittore per la fine della scrittura all'indice 1; questi indici sono analoghi ai numeri dei descrittori di file convenzionali per i flussi standard.

Dopo aver creato una pipe, si utilizzano le funzioni di I / O POSIX per scrivere alla fine della scrittura o leggere dalla fine della lettura:

Processo 1:

ssize_t bytes_written = write(pipefds[1], "Hello, World!", 14);

Processo 2:

char buffer[256];
ssize_t bytes_read = read(pipefds[0], buffer, sizeof(buffer));

In alternativa, si può invece utilizzare fdopen() per avvolgere una delle due estremità di una pipe in una struttura FILE da usare con le funzioni di C stdio:

FILE *write_end = fdopen(pipefds[1]);

if (write_end) {
    fputs("Hello, World!");
}

I pipe hanno buffer di I / O finiti e le normali scritture su pipe con buffer completi si bloccheranno. I pipe non sono quindi un meccanismo sicuro per un thread per comunicare con se stesso, come se il thread che scrive su una pipe sia anche l'unico che legge da esso, quindi non appena un write blocca quel thread è deadlock.

Una pipe persiste fintanto che ogni processo ha una descrizione di file aperta per entrambe le estremità del tubo. La funzione close (2) può essere utilizzata per chiudere un'estremità del tubo rappresentata da un descrittore di file e fclose (3) può essere utilizzato per chiudere un'estremità del tubo tramite un FILE avvolto attorno ad esso.

Stabilire un pipe per un processo figlio

I descrittori di file e gli oggetti FILE sono risorse per processo che non possono essere scambiate tra processi tramite I / O ordinario. Pertanto, affinché due processi distinti possano comunicare tramite una pipe anonima, uno o entrambi i processi partecipanti devono ereditare un'estremità di pipe aperta dal processo che ha creato la pipe, che deve quindi essere il processo padre o un antenato più distante. Il caso più semplice è quello in cui un processo padre vuole comunicare con un processo figlio.

Poiché il processo figlio deve ereditare la descrizione del file aperto necessaria dal suo genitore, è necessario prima creare la pipe. Il genitore allora si biforca. Normalmente, ogni processo sarà strettamente o un lettore o rigorosamente uno scrittore; in tal caso, ognuno deve chiudere l'estremità del tubo che non intende utilizzare.

void demo() {
    int pipefds[2];
    pid_t pid;

    // Create the pipe
    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (pid = fork()) {
        case -1:
            // error - abort ...
            break;
        case 0:   /* child */
            close(pipefds[0]);
            write(pipefds[1], "Goodbye, and thanks for all the fish!", 37);
            exit(0);
        default:  /* parent */
            close(pipefds[1]);

            char buffer[256];
            ssize_t nread = read(pipefds[0], sizeof(buffer) - 1);

            if (nread >= 0) {
                buffer[nread] = '\0';
                printf("My child said '%s'\n", buffer);
            }

            // collect the child
            wait(NULL);

            break;
    }
}

Collegamento di due processi figlio tramite una pipe

La connessione di due processi figlio tramite una pipe viene eseguita collegando ciascuno di due figli al genitore tramite estremità diverse della stessa pipa. Di solito, il genitore non sarà parte della conversazione tra i bambini, quindi chiude le copie di entrambe le estremità del tubo.

int demo() {
    int pipefds[2];
    pid_t child1, child2;

    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            write(pipefds[1], "Hello, brother!", 15);
            exit(0);
        default:  /* parent */
            // nothing
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 2 */
            char buffer[256];
            ssize_t nread;

            close(pipefds[1]);
            nread = read(pipefds[0], buffer, sizeof(buffer) - 1);
            if (nread < 0) {
                // handle error
            } else {
                buffer[nread] = '\0';
                printf("My brother told me '%s'\n", buffer);
            }
            exit(0);
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    if (child1 >= 0) {
        wait(NULL);
        if (child2 >= 0) {
            wait(NULL);
        }
    }
}

Creazione di una pipeline in stile shell

Una pipeline in stile shell è composta da due o più processi, ognuno con il suo output standard collegato allo standard input del successivo. Le connessioni output-to-input sono costruite su pipe. Per stabilire un insieme di processi simili a una pipeline, si creano processi figli collegati alla conduttura come descritto in un altro esempio e inoltre si utilizza la funzione dup2 (2) per duplicare ogni estremità della tubazione sul descrittore di file standard appropriato del proprio processo. In genere è consigliabile chiudere il descrittore del file pipe-end originale, soprattutto se, come spesso accade, si intende che il bambino esegua successivamente un comando diverso.

// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
    int pipefds[2];
    pid_t child1 = -1, child2 = -1;

    pipe(pipefds);

    switch (child1 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            dup2(pipefds[1], STDOUT_FILENO);
            close(pipefds[1]);
            execl("/bin/cat", "cat", "/etc/motd", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    switch (child2 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 2 */
            close(pipefds[1]);
            dup2(pipefds[0], STDIN_FILENO);
            close(pipefds[0]);
            execl("/bin/grep", "grep", "[Ww]ombat", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    wait(NULL);
    wait(NULL);
}

Uno degli errori più comuni nella configurazione di una pipeline è quello di dimenticare che uno o più dei processi coinvolti chiudono le estremità del tubo che non sta utilizzando. Come regola generale, il risultato finale dovrebbe essere che ogni estremità del tubo è di proprietà di un solo processo ed è aperta solo in quel processo.

In un caso come la funzione dimostrativa sopra, se il secondo figlio o il genitore chiudono pipefds[1] , il secondo figlio si bloccherà, poiché grep continuerà ad attendere l'input fino a quando non vedrà EOF, e ciò non sarà osservato fino a quando la fine della scrittura della pipe rimane aperta in qualsiasi processo, ad esempio il processo padre o il secondo figlio stesso.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow