수색…
소개
기본 생성 및 사용
익명 파이프 또는 단순히 파이프는 프로세스에 대해 파일 설명자 쌍 (하나는 읽기 종단 용, 하나는 쓰기 종단 용)으로 공개되는 커널 관리 객체입니다. 그것들은 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를 볼 때까지 입력 대기를 계속하기 때문에 두 번째 자식은 걸리지 않습니다. 파이프의 쓰기 끝이 상위 프로세스 또는 두 번째 자식 자체와 같은 프로세스에서 열려있는 한 관찰됩니다.