Szukaj…


Wprowadzenie

Rury są mechanizmem jednokierunkowej komunikacji międzyprocesowej lub przejściowej w ramach jednej maszyny. Logicznie rzecz biorąc, potok składa się z dwóch połączonych terminali, jednego, do którego można zapisać dane, a drugiego, z którego dane można następnie odczytać, z buforem danych między takim, że zapis i odczyt nie muszą być synchroniczne. Rury należy odróżnić od rurociągów powłokowych, które są zastosowaniem rur.

Podstawowe tworzenie i użytkowanie

Anonimowe potoki lub po prostu potoki są obiektami zarządzanymi przez jądro, narażonymi na procesy jako para deskryptorów plików, jeden dla zakończenia odczytu i jeden dla zakończenia zapisu. Są tworzone za pomocą funkcji pipe (2):

int pipefds[2];
int result;

result = pipe(pipefds);

Po pomyślnym zakończeniu pipe() zapisuje deskryptor końca odczytu potoku o indeksie 0 podanej tablicy, a deskryptor końca zapisu o indeksie 1; wskaźniki te są analogiczne do konwencjonalnych numerów deskryptorów plików dla standardowych strumieni.

Po utworzeniu potoku używa się funkcji I / O POSIX do zapisu na końcu zapisu lub odczytu na końcu odczytu:

Proces 1:

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

Proces 2:

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

Alternatywnie można zamiast tego użyć funkcji fdopen() aby zawinąć jeden z obu końców rury w strukturę FILE do użytku z funkcjami C stdio:

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

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

Rury mają skończone bufory we / wy, a zwykłe zapisy do rur z pełnymi buforami będą blokowane. Rury nie są zatem bezpiecznym mechanizmem dla nici do komunikowania się ze sobą, tak jakby wątek zapisujący na rurze był również jedynym, który czyta z niej, to gdy tylko blok zapisu zapisze ten wątek, zostanie zablokowany.

Potok trwa tak długo, jak długo dowolny proces ma otwarty opis pliku dla jednego z końców potoku. Funkcji close (2) można użyć do zamknięcia końca potoku reprezentowanego przez deskryptor pliku, a fclose (3) można użyć do zamknięcia końca potoku za pomocą owiniętego wokół niego FILE .

Ustanawianie potoku do procesu potomnego

Deskryptory plików i obiekty FILE są zasobami na proces, których same nie mogą być wymieniane między procesami za pomocą zwykłych operacji we / wy. Dlatego, aby dwa odrębne procesy mogły komunikować się za pośrednictwem anonimowego potoku, jeden lub oba uczestniczące procesy muszą dziedziczyć otwarty koniec potoku z procesu, który utworzył potok, który musi być zatem procesem nadrzędnym lub bardziej odległym przodkiem. Najprostszym przypadkiem jest ten, w którym proces nadrzędny chce komunikować się z procesem potomnym.

Ponieważ proces potomny musi odziedziczyć wymagany otwarty opis pliku od swojego rodzica, potok musi zostać utworzony jako pierwszy. Rodzic następnie widelec. Zwykle każdy proces będzie albo ściśle czytelnikiem, albo pisarzem; w takim przypadku każdy powinien zamknąć koniec rury, którego nie zamierza używać.

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

Łączenie dwóch procesów potomnych za pomocą potoku

Łączenie dwóch procesów potomnych za pomocą potoku odbywa się poprzez podłączenie każdego z dwóch potomków do rodzica za pomocą różnych końców tego samego potoku. Zwykle rodzic nie bierze udziału w rozmowie między dziećmi, więc zamyka kopie obu końców rur.

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

Tworzenie potoku w stylu powłoki

Potok w stylu powłoki składa się z dwóch lub więcej procesów, każdy ze standardowym wyjściem podłączonym do standardowego wejścia następnego. Połączenia wyjścia z wejściem są zbudowane na rurach. Aby ustanowić zestaw procesów podobny do potoku, tworzy się procesy potomne połączone z potokiem, jak opisano w innym przykładzie, a ponadto używa się funkcji dup2 (2), aby powielić każdy koniec potoku na odpowiednim standardowym deskryptorze pliku jego procesu. Zasadniczo dobrym pomysłem jest następnie zamknięcie oryginalnego deskryptora pliku końca potoku, szczególnie jeśli, jak to często bywa, dziecko zamierza później wykonać inną komendę.

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

Jednym z najczęstszych błędów podczas konfigurowania rurociągu jest zapomnienie o tym, że jeden lub więcej procesów bierze udział w zamykaniu końców rur, których nie używa. Zasadniczo wynik końcowy powinien być taki, że każdy koniec rury jest własnością dokładnie jednego procesu i jest otwarty tylko w tym procesie.

W przypadku, takim jak powyższa funkcja demonstracyjna, niepowodzenie drugiego dziecka lub rodzica w zamykaniu pipefds[1] spowoduje zawieszenie drugiego dziecka, ponieważ grep będzie nadal czekał na dane wejściowe, dopóki nie zobaczy EOF, i to nie będzie obserwowane tak długo, jak długo koniec zapisu potoku pozostaje otwarty w dowolnym procesie, takim jak proces nadrzędny lub samo drugie dziecko.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow