[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
명령으로 넣는다.