파이프
-
파이프는 프로세스가 파이프 객체 생성을 요청하면 커널은 프로세스가 사용할 수 있는 메모리 공간 (버퍼)를 할당하고 그 버퍼에 접근할 수 있는 두 개의 파일 디스크립터를 프로세스에게 알려준다.
-
파이프 버퍼에 저장되는 데이터는 바이트 흐름으로 취급된다. 두 개의 파일 디스크립터 중 하나는 읽기 작업 전용 나머지 하나는 쓰기 작업 전용으로 사용할 수 있다. 파이프 버퍼에 저장되는 데이터는 바이트 흐름으로 취급된다.
-
두 개의 파일 디스크립터 중 하나는 읽기 작업 전용 나머지 하나는 쓰기 작업 전용으로 사용할 수 있다. 두 디스크립터 사이에서의 읽기 / 쓰기 작업의 혼용은 허용되지 않는다.
#include <unistd.h>
int pipe(int pipefd[2]);
-
인자: pipefd[2]: 파이프 버퍼에 접근하기 위한 두 개의 파일 디스크립터
-
파이프를 생성하기 위해서는
pipe
함수를 이용할 수 있다. -
두 개의 디스크립터 중
pipefd[0]
은 읽기 영역의 끝을 가리키며pipefd[1]
은 읽기 영역의 끝을 가리킨다. -
파이프 또한 입출력 중 한 가지 방법이다. 따라서 저수준 입출력 함수를 이용하여 읽기/쓰기 작업을 할 수 있다.
-
파이프를 단일 프로세스에서 사용할 때에는 커널에서 제공받은 저장공간일 뿐 활용도가 업다. 파이프를 프로세스 사이의 통신을 위해 사용하려면
fork()
함수와 함께 사용해야 한다. -
부모 프로세스에서 파이프를 생성하고
fork()
를 통해서 자식 프로세스를 생성하면 파이프에 접근할 수 있는 파일 디스크립터들이 복사된다. 따라서 부모 프로세스와 자식 프로세스 사이의 데이터 교환이 가능해지며 또한, 자식 프로세스들끼리도 파이프를 이용하여 통신하는 것이 가능하다. -
파이프는 프로세스 간 통신을 할 때 해당 파이프에 대해서 쓰기 작업을 수행하는 프로세스와 읽기 작업을 하는 프로세스를 정해서 한 방향으로 통신하는 것이 일반적이다.
-
물론 두 프로세스 또는 그 이상의 프로세스가
write()
함수를 이용하여 파이프에 데이터를 쓰는 것이 가능하지만, 그러나 파이프는 데이터를 연속된 바이트의 흐름으로 처리하기 때문에, 프로세스 별로 쓴 데이터를 구별해주지 않고 파이프 버퍼에 쌓일 뿐이다.
-
위의 그림처럼
fork()
함수 이후에 사용하지 않는 프로세스 A의pipefd[0]
과 프로세스 B의pipefd[1]
을close()
함수를 통해서 닫아주는 것이 좋다. -
이유는 파이프 통신의 종료와 연관되어 있는데, 파이프
write()
에 해당하는 파일 디스크립터로close()
함수를 호출하면read()
함수는 버퍼에 있는 모든 데이터를 읽고EOF
를 만난다. -
따라서
read()
함수를 호출한 프로세스는 상대방 프로세스가 파이프 연결을 끊었다는 사실을read()
함수의 반환값으로 인지할 수 있다. -
시스템 단위로 운영되는 ‘열린 파일 테이블’의 엔트리는 하나의 파일 디스크립터라도 해당 엔트리를 가리키고 있다면 없어지지 않는다. 만약 프로세스 A의
pipefd[0]
이 열려 있더라면, 프로세스 B가pipefd[0]
을 닫아도 ‘열린 파일 테이블의’ 해당 엔트리를 사라지지 않으므로 프로세스 A는 프로세스 B가 파이프를 닫았다는 사실을 알 방법이 없다.
예제
- 부모 프로세스는 사용자에게 문자열을 입력받는다.
- 부모 프로세스는 자식 프로세스에게 파이프를 통해서 사용자가 입력한 문자열을 전송한다.
- 자식 프로세슨느 부모 프로세스가 전달한 문자열을 파일에 쓴다.
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void error_proc(const char *);
int main(int argc, char *argv[]) {
int pipe_fd[2];
int res;
char buff[BUFSIZ];
pid_t pid;
int read_len, n_write;
int open_fd, status;
if (argc != 2) {
fprintf(stderr, "usage: %s [file] \n", argv[0]);
}
res = pipe(pipe_fd);
if (res == -1) error_proc("pipe");
pid = fork();
if (pid == -1) error_proc("fork");
if (pid == 0) { // child
close(pipe_fd[1]);
open_fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC);
while (1) {
read_len = read(pipe_fd[0], buff, BUFSIZ - 1);
if (read_len == -1) error_proc("read");
if (read_len == 0) break;
write(open_fd, buff, read_len);
}
printf("parent process closed the pipe. \n");
close(open_fd);
close(pipe_fd[0]);
return 0;
} else { // parent
close(pipe_fd[0]);
while (1) {
fgets(buff, BUFSIZ - 1, stdin);
read_len = strlen(buff);
if (read_len == 4 && !strncmp(buff, "END", 3))
break;
n_write = write(pipe_fd[1], buff, read_len);
if (n_write == -1) error_proc("write");
printf("%d bytes are written \n", n_write);
}
close(pipe_fd[1]);
wait(&status);
return 0;
}
}
void error_proc(const char *str) {
fprintf(stderr, "%s: %s\n", str, strerror(errno));
exit(1);
}
참고 문헌
>> Home