Sök…


Introduktion

Rör är en mekanism för enkelriktad interprocess eller intertrådskommunikation inom ramen för en enda maskin. Logiskt består ett rör av två anslutna terminaler, en till vilken data kan skrivas, och en annan från vilken dessa data därefter kan läsas, med en databuffert mellan sådana att skrivningar och läsningar inte krävs för att vara synkrona. Rör bör särskiljas från skalledningar som är en applikation av rör.

Grundläggande skapande och användning

Anonyma rör, eller helt enkelt rör, är kärnhanterade objekt som exponeras för processer som ett par filbeskrivningar, ett för lästerminalen och ett för skrivterminalen. De skapas via pipe (2):

int pipefds[2];
int result;

result = pipe(pipefds);

Vid framgång registrerar pipe() deskriptorn för den lästa änden av röret vid index 0 för den medföljande matrisen, och deskriptorn för skrivänden vid index 1; dessa index är analoga med de konventionella filbeskrivningsnumren för standardströmmarna.

Efter att ha skapat ett rör använder man POSIX I / O-funktioner för att skriva till skrivändet eller läsa från läsändet:

Process 1:

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

Process 2:

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

Alternativt kan man istället använda fdopen() att linda in en av båda rörändarna i en FILE struktur för användning med C stdio-funktioner:

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

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

Rör har begränsade I / O-buffertar, och vanliga skrivningar till rör med fulla buffertar kommer att blockeras. Rör är därför inte en säker mekanism för en tråd att kommunicera med sig själv, som om trådskrivningen till ett rör också är den enda som läser från den, så snart en skrivblock är den tråden låst in.

Ett rör kvarstår så länge som någon process har en öppen filbeskrivning för någon av rörändarna. Funktionen close (2) kan användas för att stänga en rörände som representeras av en filbeskrivning, och fclose (3) kan användas för att stänga en rörände via en FILE lindad runt den.

Upprätta ett rör till en barnprocess

Filbeskrivare och FILE objekt är resurser per process som inte själva kan utbytas mellan processer via vanlig I / O. Därför, för att två distinkta processer ska kommunicera via ett anonymt rör, måste en eller båda deltagande processer ärva ett öppet rörände från processen som skapade röret, vilket därför måste vara moderprocessen eller en mer avlägsen förfader. Det enklaste fallet är det där en förälderprocess vill kommunicera med en barnprocess.

Eftersom barnprocessen måste ärva den nödvändiga öppna filbeskrivningen från sin överordnade måste röret skapas först. Föräldern gafflar sedan. Vanligtvis kommer varje process antingen att vara en läsare eller en författare; i så fall bör var och en stänga röränden som den inte tänker använda.

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;
    }
}

Ansluta två barnprocesser via ett rör

Anslutning av två barnprocesser via ett rör utförs genom att ansluta vart och ett av två barn till föräldern via olika ändar på samma rör. Vanligtvis är föräldern inte part i konversationen mellan barnen, så den stänger sina kopior av båda rörändarna.

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);
        }
    }
}

Skapa en skal-stil pipeline

En skal-stil pipeline består av två eller flera processer, var och en med sin standardutgång ansluten till nästa standardingång. Utgångarna till anslutningarna är byggda på rör. För att upprätta en pipeline-liknande uppsättning processer skapar man rörkopplade barnprocesser som beskrivs i ett annat exempel och använder dessutom dup2 (2) -funktionen för att duplicera varje rörände till lämplig standardfilbeskrivare för dess process. Det är i allmänhet en bra idé att sedan stänga den ursprungliga pip-end-filbeskrivaren, särskilt om man, som ofta är fallet, avser att barnet efteråt kan utföra ett annat kommando.

// 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);
}

Ett av de vanligaste felen vid installation av en rörledning är att glömma att ha en eller flera av de processer som är inblandade stänga rörändarna som den inte använder. Som en tumregel bör slutresultatet vara att varje rörände ägs av exakt en process och endast är öppen i den processen.

I ett fall som demofunktionen ovan kommer det andra barnet eller föräldern att stänga pipefds[1] att resultera i att det andra barnet hänger, för grep fortsätter att vänta på input tills det ser EOF, och det kommer inte att vara observeras så länge skrivänden på röret förblir öppen i vilken process som helst, till exempel överordnadsprocessen eller det andra barnet själv.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow