수색…


소개

파이프는 단일 시스템의 범위 내에서 단방향 프로세스 간 또는 스레드 간 통신을위한 메커니즘입니다. 논리적으로 파이프는 두 개의 연결된 말단, 즉 데이터를 쓸 수있는 말단과 그 데이터를 나중에 읽을 수있는 말단으로 구성되며, 쓰기와 읽기가 동기 일 필요가없는 데이터 버퍼가 있습니다. 파이프는 파이프 를 사용하는 쉘 파이프 라인 과 구별되어야합니다.

기본 생성 및 사용

익명 파이프 또는 단순히 파이프는 프로세스에 대해 파일 설명자 쌍 (하나는 읽기 종단 용, 하나는 쓰기 종단 용)으로 공개되는 커널 관리 객체입니다. 그것들은 pipe (2) 함수를 통해 생성됩니다 :

int pipefds[2];
int result;

result = pipe(pipefds);

성공시, pipe() 는 제공된 배열의 인덱스 0에 파이프의 읽기 끝에 대한 디스크립터를 기록하고 인덱스 1에 쓰기 엔드에 대한 디스크립터를 기록한다. 이러한 인덱스는 표준 스트림에 대한 일반적인 파일 설명자 수와 유사합니다.

파이프를 만들면 POSIX I / O 함수를 사용하여 쓰기 끝으로 쓰거나 읽기 끝에서 읽습니다.

프로세스 1 :

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

프로세스 2 :

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

또는 대신 fdopen() 을 사용하여 두 개의 파이프 끝 중 하나를 FILE 구조로 감싸서 C stdio 함수와 함께 사용할 수 있습니다.

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

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

파이프에는 유한 I / O 버퍼가 있으며 전체 버퍼가있는 파이프에 대한 일반 쓰기가 차단됩니다. 따라서, 파이프는 쓰레드가 자신과 통신하기위한 안전한 메커니즘이 아닙니다. 마치 파이프에 쓰는 쓰레드가 유일한 쓰레드이기도하고, 쓰기 쓰레드가 데드락을 막 자마자 곧바로 읽습니다.

모든 프로세스가 파이프 끝 중 하나에 대한 열린 파일 설명을 가지고있는 한 파이프는 지속됩니다. close (2) 함수는 파일 디스크립터에 의해 표현 된 파이프 끝을 닫는 데 사용될 수 있고, fclose (3)는 FILE 통해 파이프 끝을 닫는 데 사용될 수 있습니다.

하위 프로세스에 대한 파이프 설정

파일 디스크립터와 FILE 객체는 일반 I / O를 통해 프로세스간에 자체적으로 교환 할 수없는 프로세스 별 리소스입니다. 따라서 두 개의 별개 프로세스가 익명 파이프를 통해 통신하려면 참여 프로세스 중 하나 또는 둘 모두가 파이프를 만든 프로세스의 열린 파이프 끝을 상속해야합니다. 따라서이 프로세스는 부모 프로세스이거나 먼 조상이어야합니다. 가장 간단한 경우는 상위 프로세스가 하위 프로세스와 통신하려고하는 경우입니다.

하위 프로세스는 부모로부터 필요한 열린 파일 설명을 상속해야하므로 파이프를 먼저 만들어야합니다. 그런 다음 부모가 분기합니다. 일반적으로 각 프로세스는 엄격하게 독자 또는 엄격하게 작가가됩니다. 이 경우 각 파이프는 사용하지 않을 파이프 끝을 닫아야합니다.

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

파이프를 통해 두 개의 하위 프로세스 연결하기

파이프를 통해 두 개의 하위 프로세스를 연결하는 것은 동일한 파이프의 다른 끝을 통해 두 자식을 부모에 연결하여 수행됩니다. 일반적으로 부모는 자식 간의 대화에 참여하지 않으므로 두 파이프 끝 사본을 닫습니다.

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

셸 스타일 파이프 라인 만들기

쉘 스타일의 파이프 라인은 두 개 이상의 프로세스로 구성되며, 각 프로세스는 표준 출력이 다음 출력의 표준 입력에 연결됩니다. 출력 대 입력 연결은 파이프를 기반으로합니다. 파이프 라인과 같은 일련의 프로세스를 구축하기 위해 다른 예에서 설명한대로 파이프 연결 하위 프로세스를 생성하고 dup2 (2) 함수를 사용하여 각 파이프 끝을 해당 프로세스의 적절한 표준 파일 설명자에 복제합니다. 일반적으로 원래의 파이프 - 엔드 파일 디스크립터를 닫는 것이 좋습니다. 특히, 흔히 그렇듯이 자식이 exec 후에 다른 명령을 사용하려는 경우에 특히 좋습니다.

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

파이프 라인 설정에서 흔히 발생하는 오류 중 하나는 관련된 프로세스 중 하나 이상이 사용하지 않는 파이프 끝을 닫는 것을 잊는 것입니다. 경험적으로 최종 결과는 각 파이프 끝이 정확히 하나의 프로세스에 의해 소유되고 해당 프로세스에서만 열려 있다는 것입니다.

위의 데모 기능과 같은 경우, 두 번째 자식이나 부모가 pipefds[1] 를 닫지 pipefds[1] 두 번째 자식이 pipefds[1] 됩니다. grep 은 EOF를 볼 때까지 입력 대기를 계속하기 때문에 두 번째 자식은 걸리지 않습니다. 파이프의 쓰기 끝이 상위 프로세스 또는 두 번째 자식 자체와 같은 프로세스에서 열려있는 한 관찰됩니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow