[OS] Pipe
Inter Process Communication (IPC) 방법 중 하나로, OS에서 process의 standard 출력을 다른 process의 standard input에 연결하는 방식이다. Process간 단방향 통신의 한 방법이며 동기화를 기본적으로 제공한다. Signal은 1~31번까지 번호만 전달할 수 있지만, pipe는 data를 전송 할 수 있다.
가득 차거나 비어 있을 때 자동으로 block 된다. Linux prompt에선 |를 통해 사용 가능하다.
pipe
System call 함수로, 읽기 전용 파일과 쓰기 전용 파일이 따로 존재한다.
#include <unistd.h> int pipe(int filedes[2]);
| Argument | Description |
|---|---|
| int filedes[2] | filedes[0]: 읽기 전용 filedes[1]: 쓰기 전용 |
| Return | Description |
|---|---|
| 0 | 성공 했을 때 |
| -1 | EMFILE: 너무 많은 파일 디스크립터가 프로세스에 의해 사용되고 있다. ENFILE: 시스템 파일 테이블이 꽉찼을경우 EFAULT: filedes 가 유효하지 못하다. ENOBUFS: 시스템에 연산을 위해서 이용할수 있는 자원이 부족할때 |
pipe는 기본적으로 kernel 영역 메모리를 쓰기 때문에, write(), read() system call을 통해 쓰기/읽기가 가능하다. pipe를 썼을 때 구조는 다음과 같다. 참고로 fd 0번은 stdin, 1번은 stdout이다.

Example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define MSGSIZE 7
char *msg1 = "Shumin1";
char *msg2 = "Shumin2";
char *msg3 = "Shumin3";
int main() {
char inbuf[1024];
int p[2], j;
int ret;
if (pipe(p) < 0) {
perror("ERROR: pipe call");
exit(1);
}
/* write down pipe */
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
while(1) {
ret = read(p[0], inbuf, 10);
write( 1, inbuf, ret );
getchar();
}
return 0;
}
$ ./a.out Shumin1Shu min2Shumin 3
위 결과를 보면 pipe에 넣은 문자열이 read(p[0], inbuf, 10);로 인해 10개 문자씩 순서에 맞춰서 출력이 되는 것을 볼 수 있다.
Child Process와 Pipe의 관계
만약 pipe를 연결 후에 fork()를 통해 child process를 생성하게 되면, pipe가 연결된 모습은 아래와 같을 것이다.

Pipe의 input과 output의 reference count는 둘 다 2로 나타난다. 이 때 parent의 pipe write을 닫고, child read를 닫으면 단방향 process 통신을 가능하게 한다. 예시 프로그램은 다음과 같이 작성 할 수 있다.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
int n, fd[2];
char buf[255];
int pid;
if (pipe(fd) < 0) {
perror("pipe error : ");
exit(0);
}
// 파이프를 생성한다.
if ((pid = fork()) < 0) {
perror("fork error : ");
exit(0);
} else if (pid == 0) { // 만약 자식프로세스라면 파이프에 자신의 PID(:12) 정보를 쓴다.
close(fd[0]);
while(1) {
memset(buf, 0x00, 255);
sprintf(buf, "Hello : %d\n", getpid());
write(fd[1], buf, strlen(buf));
sleep(1);
}
} else { // 만약 부모프로세스(:12)라면 파이프(:12)에서 데이타를 읽어들인다.
close(fd[1]);
while(1) {
memset(buf, 0x00, 255);
n = read(fd[0], buf, 255);
fprintf(stderr, "%s", buf);
}
}
}
$ ./a.out Hello : 14049 Hello : 14049 Hello : 14049 ---------------------------- $ ps -ef | grep a.out | grep -v grep shumin 14048 13552 0 20:46 pts/14 00:00:00 ./a.out shumin 14049 14048 0 20:46 pts/14 00:00:00 ./a.out
위 결과를 보면 child process ID를 pipe로 전달해서 parent쪽으로 전달해서 출력해주는 것을 볼 수 있다.
Example (Process 간 양방향 통신)
다음은 client와 server가 서로 양방향 통신을 하는 프로그램을 소개한다. 아래 코드는 client에서 server로 읽고자 하는 파일명을 전달하면 해당 파일 내용을 server에서 client로 전달하는 프로그램이다.
// pipe.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void client(int ,int);
void server(int ,int);
int main(void) {
int childpid, pipe1[2], pipe2[2];
if(pipe(pipe1) < 0 || pipe(pipe2) < 0)
printf("pipe error");
childpid = fork();
if(childpid > 0) { /* parent process */
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
while(wait((int *) 0) != childpid);
close(pipe1[1]);
close(pipe2[0]);
} else { /* child process */
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
close(pipe1[0]);
close(pipe2[1]);
}
}
// client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXBUFF 1024
void client (int readfd, int writefd) {
char buff[MAXBUFF];
int n;
if(fgets(buff, MAXBUFF, stdin) == NULL)
printf("client: filename read error");
n = strlen(buff);
if(buff[n-1] == '\n')
n--;
if(write(writefd, buff, n) != n)
printf("client: filename write error");
while((n = read(readfd, buff, MAXBUFF)) > 0)
if(write(1, buff, n) != n)
printf("client: data write error");
if(n < 0)
printf("client: data read error");
}
// server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXBUFF 1024
void server (int readfd, int writefd) {
char buff[MAXBUFF];
int n, fd; extern int errno;
if ((n = read(readfd, buff, MAXBUFF)) <= 0)
printf("server: filename read error");
buff[n] = '\0';
if ((fd = open(buff, 0)) < 0) {
strcat(buff, "can't open\n");
n = strlen(buff);
if(write(writefd, buff, n) != n)
printf("server: errmesg write error");
} else {
while((n = read(fd, buff, MAXBUFF)) > 0)
if(write(writefd, buff, n) != n)
printf("server: data write error");
}
}
$ ./a.out include include include include define MAXBUFF 1024 ...
popen / pclose
위 예제 코드처럼 low level로 작성하는 번거스러움을 없애기 위해서 linux에선 library로 쉽게 단방향 pipe를 popen, pclose을 통해 제공한다.
다시 말해 실행시킨 명령어와 standard input/output을 주고 받기 위한 용도로 사용한다. (Input/Output pipe를 open하기 위한 용도로 사용함)
#include <stdio.h> FILE *popen(const char *command, const char *type); int pclose();
| Argument | Description |
|---|---|
| const char *command | 실행 할 명령어 |
| const char *type | “r”: 명령어를 실행하면 명령어가 표준출력으로 출력한 문자열을 읽기 위한 용도로 pipe를 open함 “w”: 명령어를 실행 후 사용자가 keyboard로 데이터를 입력해야 하는 명령어에 사용함. 즉, command의 표준입력으로 데이터를 전송하기 위한 pipe를 open함 |
| Return | Description |
|---|---|
| -1 | 실패 |
| 그 외 | 성공 |
// pipe.c
#include <stdio.h>
int main(void) {
FILE *pipein_fp, *pipeout_fp;
char readbuf[80];
/* Make a simplex pipe*/
pipein_fp = popen("ls", "r");
/* Make a simplex pipe*/
pipeout_fp = popen("sort", "w");
while(fgets(readbuf, 80, pipein_fp))
fputs(readbuf, pipeout_fp);
/* Close pipes */
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
$ ./a.out pipe.c
위 프로그램은 popen(), pclose()를 써서 ls 명령으로 얻은 문자열을 sort 명령으로 넣는다.