![파이프의 다른 끝 부분을 밀봉하십시오.](https://linux55.com/image/203388/%ED%8C%8C%EC%9D%B4%ED%94%84%EC%9D%98%20%EB%8B%A4%EB%A5%B8%20%EB%81%9D%20%EB%B6%80%EB%B6%84%EC%9D%84%20%EB%B0%80%EB%B4%89%ED%95%98%EC%8B%AD%EC%8B%9C%EC%98%A4..png)
나는 다음을 사용하여 IPC용 코드를 작성했습니다 pipe()
.
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void) {
char message_buffer[15] = "Hello World \n";
char read_buffer[15];
int fd[2];
int return_value = pipe(fd);
if (return_value < 0) {
printf("Error creating the pipe");
}
int rc = fork();
if (rc < 0) {
printf("Error forking a child");
}
if (rc > 0) {
close(fd[0]);
write(fd[1], message_buffer, 15);
close(fd[1]);
wait(NULL);
} else {
close(fd[1]);
read(fd[0], read_buffer, 15);
close(fd[0]);
printf("The Message: %s", read_buffer);
}
return 0;
}
배관을 처음 접하는데 다음과 같은 질문이 있습니다.
- 왜 부모가 쓰기 전에 읽는 쪽을 닫고, 쓴 후에 쓰기 쪽을 닫아야 하는지 이해가 안 가나요?
- 아이도 마찬가지인데, 읽기 전에 쓰기 쪽을 닫아야 하는 이유는 무엇이며, 읽은 후에는 읽기 쪽을 닫아야 하는 이유는 무엇입니까?
- 부모 프로세스와 자식 프로세스가 동시에 실행 중이므로 부모 프로세스가 메시지를 쓰는 동안 자식 프로세스가 메시지를 읽는다면 어떻게 될까요?
- 부모와 자식이 동시에 실행 중이므로 자식이 읽고 부모가 아직 파이프에 아무 것도 쓰지 않았다면 어떻게 될까요?
내 질문이 어리석은 것 같지만 코스 시험을 위해 일반 배관을 공부하고 있으므로 답변을 도와주세요.
답변1
정답은질문 1과 2내부에pipe
매뉴얼 페이지("예" 섹션):
분기 후 각 프로세스는 파이프에 필요하지 않은 파일 설명자를 닫습니다(pipe(7) 참조).
파이프는 단방향이므로 끝이 지정되어 있습니다.읽다끝과쓰다끝. 부모가 이 파이프를 사용하여 자식에게 데이터를 쓰는 경우 부모가 읽기 쪽을 열어 둘 필요가 없습니다. 대조적으로, 하위 프로세스가 파이프에서 데이터를 읽으려는 경우 쓰기 측을 열 필요가 없습니다.
하지만 하나더 중요한 이유파이프의 원치 않는 끝 부분을 닫는 데 사용됩니다.설명하다@ilkkachu 사용자가 작성했습니다. 링크된 답변을 참조하시기 바랍니다.
편집하다:
왜 부모는 글을 쓴 후 글쓰기 쪽을 닫아야 하는지, 아이는 왜 읽은 후에 읽는 쪽을 닫아야 하는지 물으셨습니다.
그들은 이것을 할 필요가 없습니다. 두 프로그램이 파이프를 사용하여 계속 실행되고 데이터를 교환하려면 파이프를 열어 두어야 합니다. 파이프 사용을 보여주고 메시지 전송 후 종료되는 간단한 예제 프로그램에서 부모 및 자식 프로세스는 프로그램이 종료되기 전에 리소스를 적절하게 정리하기 위해 파이프 파일 설명자를 닫을 수 있습니다.
질문 #3과 #4에 대한 답변은 다음과 같습니다.pipe(7)
매뉴얼 페이지.
귀하의 질문 #3:
부모 프로세스와 자식 프로세스가 동시에 실행될 수 있으므로 부모 프로세스가 메시지를 쓰는 동안 자식 프로세스가 메시지를 읽는다면 어떻게 될까요?
자식은 부모가 작성한 파이프에서 사용 가능한 모든 데이터를 읽을 수 있습니다. 매뉴얼 페이지에 따르면:
POSIX.1은 PIPE_BUF 바이트보다 작은 쓰기는 원자성이어야 함을 지정합니다. 출력 데이터는 연속 시퀀스로 파이프에 기록됩니다. PIPE_BUF 바이트를 초과하는 쓰기는 비원자적일 수 있습니다. 커널은 데이터를 다른 프로세스에서 쓴 데이터와 인터리브할 수 있습니다. POSIX.1에서는 PIPE_BUF가 512바이트 이상이어야 합니다. (Linux에서는 PIPE_BUF가 4096바이트입니다.)
귀하의 질문 #4:
부모와 자식이 동시에 실행될 수 있으므로 자식이 읽고 부모가 아직 파이프에 아무 것도 쓰지 않았다면 어떻게 될까요?
매뉴얼 페이지에는 다음과 같이 나와 있습니다.
프로세스가 빈 파이프에서 데이터를 읽으려고 하면 read(2)는 데이터를 사용할 수 있을 때까지 차단됩니다. 프로세스가 전체 파이프(아래 참조)에 쓰기를 시도하면 쓰기가 완료될 수 있을 만큼 파이프에서 충분한 데이터를 읽을 때까지 write(2)가 차단됩니다.
댓글의 질문에 답해 주세요.
질문 1과 2의 경우, 원치 않는 끝을 닫지 않으면 프로그램에 어떤 영향도 미치지 않는다는 의미인가요?
파이프라인의 작동을 방해해서는 안 되지만 프로그램에서 사용하는 리소스에 약간의 공간을 차지하게 됩니다. 파이프의 불필요한 끝을 닫으면 이러한 리소스가 유지되지 않습니다.
질문 3의 경우 이는 부모가 쓴 내용을 아이가 읽을 것임을 의미합니다. 부모가 작성해야 할 내용을 완료했다는 것을 아이는 어떻게 알 수 있습니까?
매뉴얼 페이지에는 다음과 같이 나와 있습니다.
파이프가 제공하는 통신 채널은 바이트 스트림입니다. 메시지 경계 개념이 없습니다.
이는 파이프가 전송하는 데이터에 관심이 없다는 것을 의미합니다. "메시지"가 무엇을 의미하는지, 부모가 쓰기를 마쳤는지 또는 더 많은 데이터를 쓰기를 원하는지 여부도 모릅니다.
"완전한 메시지"가 무엇인지 결정하려면 고유한 기술을 구현해야 합니다. 예를 들어, 부모는 \0
특수 문자(예: 파이프가 사용되는 특정 컨텍스트에서 의미가 있는 다른 문자)를 전송하여 완전한 메시지가 작성되었음을 자식에게 나타낼 수 있습니다.
답변2
"파이프 및 FIFO의 I/O" 아래에 다음과 같이 표시됩니다.
파이프의 쓰기 끝을 참조하는 모든 파일 설명자가 닫힌 경우 파이프에서 read(2)를 시도하면 파일 끝이 표시됩니다(read(2)는 0을 반환함).
파이프의 읽기 끝을 참조하는 모든 파일 설명자가 닫힌 경우 write(2)는 호출 프로세스에 대해 SIGPIPE 신호가 생성되도록 합니다.
하위 프로세스가 쓰기 측의 복사본을 닫도록 하여(사용하지 않음) 하위 프로세스가 언제 감지할 수 있는지 확인합니다.부모이 방법. 하위 프로세스가 쓰기 측을 열어두면 본질적으로 자체적으로 대기하므로 파이프에서 EOF를 볼 수 없습니다. (2)
마찬가지로 상위 프로세스가 읽기 측 복사본을 닫도록 하면 하위 프로세스가 사라지는 시기를 상위 프로세스에서 감지할 수 있습니다. (1)
가변적인 양의 데이터를 읽고 쓰려는 시도의 합계 read()
또는 반환 값을 확인하는 코드와는 다르 므로 기본적으로 SIGPIPE 신호를 받는 부모 외에는 의미가 없습니다.write()
쓰기 후에 부모의 쓰기 쪽을 닫고 자식에서 읽은 후에 읽기 쪽을 닫는 것은 일반적인 관리 작업입니다. 프로세스가 즉시 종료되면 명시적인 종료는 효과가 없습니다.
귀하의 질문 3과 4가 같은지 확실하지 않지만 읽을 내용이 없을 때 독자가 읽으면 시스템 호출이 차단됩니다.
프로세스가 빈 파이프에서 데이터를 읽으려고 하면 read(2)는 데이터를 사용할 수 있을 때까지 차단됩니다.
작성자가 다른 작업을 수행하는 동안 작성하는 경우 데이터는 운영 체제의 버퍼에 복사됩니다(적어도 충분한 공간이 있는 경우). 그렇지 않은 경우 작성자는 다음을 차단합니다.
프로세스가 전체 파이프(아래 참조)에 쓰기를 시도하면 쓰기가 완료될 수 있을 만큼 파이프에서 충분한 데이터를 읽을 때까지 write(2)가 차단됩니다.
"아래"는 "파이프라인 용량" 섹션입니다.
동시에 이 작업을 수행하면 운영 체제는 데이터만 복사합니다.
답변3
다른 사람들은 훌륭한 답변을 제공했습니다. 이것은 실험적으로 도움을 주기 위한 위의 답변에 대한 ilkkachu의 의견의 확장입니다. 원래 게시한 프로그램을 약간 수정한 다음 프로그램을 고려해 보세요.
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
struct message {
char content[15];
};
int main(void) {
int fds[2];
if (pipe(fds) < 0) {
printf("Error creating the pipe\n");
return 1;
}
srand(time(NULL));
const pid_t pid = fork();
if (pid < 0) {
printf("Error forking a child");
return 1;
}
if (pid > 0) {
const int message_count = (rand() % 9) + 1;
const struct message message_buffer = {
.content = "Hello, World\n",
};
/* 1 */ close(fds[0]);
for (int i = 0; i < message_count; ++i) {
write(fds[1], &message_buffer, sizeof(message_buffer));
}
/* 2 */ close(fds[1]);
wait(NULL);
} else {
struct message read_buffer;
/* 3 */ close(fds[1]);
while (read(fds[0], &read_buffer, sizeof(read_buffer)) > 0) {
printf("The Message: %s", read_buffer.content);
}
/* 4 */ close(fds[0]);
}
return 0;
}
이 버전은 전송되지 않습니다.ㅏ메시지를 전송하면 1에서 10 사이의 임의 개수의 메시지가 전송됩니다. 이렇게 하면 하위 프로세스는 읽어야 할 메시지 수를 미리 알 수 없습니다. 파이프에서 모든 내용을 읽었을 때 중지됩니다.그리고 파이프에는 다른 어떤 것도 쓸 수 없습니다(즉, 모든 쓰기가 닫히고 read
음수 값이 반환되는 경우) 다음은 몇 가지 실행 예시입니다.
$ ./a.out
The Message: Hello, World
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
$
close()
파이프라인과 관련된 각 호출 앞에 설명을 추가했습니다. (1)행을 주석 처리하면 프로그램 동작이 크게 바뀌지 않습니다.
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
$ ./a.out
The Message: Hello, World
The Message: Hello, World
$
(2)행만 주석 처리하면 프로그램 실행 시 교착 상태가 발생합니다. 예를 들면 다음과 같습니다.
$ ./a.out
The Message: Hello, World
The Message: Hello, World
(program hangs here)
왜? 상위 프로세스가 파이프의 쓰기 끝을 닫지 않으면 하위 프로세스는 호출에서 영원히 차단되어 read
추가 데이터를 기다립니다. ( read
만약 있다면어느파이프의 쓰기 끝과 연관된 파일 설명자를 엽니다. ) 그러면 부모는 에 대한 호출에서 영원히 차단됩니다 wait
. 부모와 자식은 서로를 영원히 기다립니다.
(3)행만 주석 처리하면 프로그램 실행 시 교착 상태가 발생합니다. 예를 들면 다음과 같습니다.
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
(program hangs here)
왜? 마찬가지로 read
쌍에 대한 호출은 존재하는 한 차단됩니다.어느파이프의 쓰기 끝과 연관된 파일 설명자를 열면 하위 프로세스에 하나가 있습니다. 결과적으로 자녀는 다시 통화가 차단되고 read
, 부모는 통화가 차단 wait
되며 더 이상 진행되지 않습니다.
마지막으로 (4)행을 주석 처리하면 프로그램 동작이 크게 바뀌지 않습니다.
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
$ ./a.out
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
The Message: Hello, World
$
모든 프로세스가 필요하지 않은 파일 설명자를 닫도록 함으로써 결코 오지 않는 파이프에서 데이터를 읽기 위해 대기하는 프로세스가 차단되지 않도록 할 수 있습니다.
답변4
어떤 경우에는 사용되지 않는 파이프 설명자를 적절하게 닫지 못하면 중요한 결과를 초래할 수 있습니다.
- 작성자가 읽기 설명자를 꺼야 하는 이유
판독기가 없는 파이프에 쓰기를 시도하면 이 SIGPIPE
신호가 생성됩니다. 이는 리더가 떠날 때 발생해야 합니다(예: 프로세스가 종료됨).
그러나 기록기가 파이프의 읽기 설명자를 열어 두는 동안에는 SIGPIPE
기술적으로 파이프가 손상되지 않고 기록기 자체가 가능한 판독기이기 때문에 아무 일도 일어나지 않습니다.
- 독자가 쓰기 설명자를 꺼야 하는 이유
파이프를 닫으면 EOF 조건이 생성됩니다. 판독기가 쓰기 설명자의 복사본을 닫지 않으면 판독기는 여전히 파이프에 더 많은 데이터를 쓸 수 있으므로 기록기는 EOF를 보낼 수 없습니다.