OS,  Study

[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]);
ArgumentDescription
int filedes[2]filedes[0]: 읽기 전용
filedes[1]: 쓰기 전용
ReturnDescription
0성공 했을 때
-1EMFILE: 너무 많은 파일 디스크립터가 프로세스에 의해 사용되고 있다.
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();
ArgumentDescription
const char *command실행 할 명령어
const char *type“r”: 명령어를 실행하면 명령어가 표준출력으로 출력한 문자열을 읽기 위한 용도로 pipe를 open함
“w”: 명령어를 실행 후 사용자가 keyboard로 데이터를 입력해야 하는 명령어에 사용함. 즉, command의 표준입력으로 데이터를 전송하기 위한 pipe를 open함
ReturnDescription
-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 명령으로 넣는다.

Leave a Reply

Your email address will not be published. Required fields are marked *