본문 바로가기

CS/유닉스프로그래밍

10. 유닉스 IPC - PIPE, FIFO

PIPE

프로그램을 작성하는데, 그 프로그램이 한 가지만 잘하는 프로그램을 짜자는 거

한 가지만 잘하는 프로그램을 여러개 만들어서 묶어서 함께 일할 수 있게 하자는 거

그게 가능하려면 프로세스간 커뮤니케이션이 되어야 함 IPC

 

IPC의 대표적인 방법이 파이프

시그널을 통해서도 커뮤니케이션할 수 있는데, 데이터도 주고받고 하기 힘듦

PIPE로 데이터를 주고 받을 수 있는 텍스트 데이터 스트림 채널을 제공함

 

파이프는 제일 오래된 유닉스의 IPC form이고 가장 간단함

파이프도 file로 간주됨, special file로 표현됨 open하지는 않고 read write를 함

 

 

who는 시스템 내에 어떤 유저가 있는지, 유저네임을 출력하는 프로그램임

그 출력을 temp 파일에 write함

wc는 word count임 유저들의 이름이 word 단위로 나오는데, 그 word를 count 하는 프로그램

그럼 유저의 수임. 이 후에는 temp가 필요가 없어져서 지움

 

pipe에는 포트가 두개 있음 write(1)로 출력하고 read(0)으로 받음

0,1,2번을 바꾸는 일은 shell이 해줌

shell이 who라는 프로세스와, wc라는 프로세스를 만들어주고 또 쉘이 중간에 파이프를 만들어줌

이 두 개는 같은 그룹 아이디를 가짐, 먼저 만들어진게 그룹 리더가 됨

 

#include <unistd.h>
int pipe(int filedes[2]);
// returns : 0 if ok, -1 on error

 

filedes[0]은 pipe의 read 쪽이고, filedes[1]은 pipe의 write 쪽

데이터가 들어가는 순서는 바뀌지 않고, lseek도 사용되지 않음, 디스크에서만 쓸 수 있음

 

이 파이프는 half duplex로 사용됨, 반드시 그런 건 아니고 full duplex로 쓸 수도 있음

parent와 child 사이에서 사용함 

parent가 파이프를 만들 건데 child가 만들어지면 그 만들어진 파이프의 파일디스크립터 변수를 공유할 거임

그래서 둘이 데이터를 주고 받을 수 있음

parent와 child로 국한하지 않고, 여러 child를 만들었다면 그 children 끼리도 공유 가능함

 

errno

cause

EMFILE

more than MAX_OPEN-2 file descriptors already in use by this process

ENFILE

number of simultaneously open files in system would exceed system-imposed limit

 

read를 하려는데 파이프가 비어있다면  written 될 때까지 block 됨 데이터가 있다면 그 데이터를 즉각 받아서 return

write를 하려는데 파이프가 꽉 차있다면 write해야하는 프로세스가 여유공간이 생길 때까지 block

 

read를 하려는 write를 하려는 두 프로세스 중 하나가 terminate될 수도 있음

write쪽이 먼저 close하면 read 프로세스는 남은 데이터를 read하고,

pipe가 empty가 되면 마지막에 EOF를 읽고 0을 즉각 반환

read쪽이 먼저 close하면 커널에서 write하는 프로세스에 SIGPIPE 시그널을 보냄

그럼 write하는 프로세스는 SIGPIPE의 default action으로 termination

그러지 않으려면 catch해서 handler 수행하면 됨 아니면 ignore setting 해도 됨

 

pipe에 대한 write signal은 slow device system call로 간주함 

그래서 이 시스템콜이 실패하면 -1을 return 하고 interrupt가 걸리고 다음 명령어를 실행함

 

char *msg1 = "hello, world #1";
char *msg2 = "hello, world #2";
char *msg3 = "hello, world #3";

main(){
   char inbuf[MSGSIZE];
   int p[2], j;

   /* 파이프를 개방한다 */
   pipe(p);

   /* 파이프에 쓴다 */
   write(p[1], msg1, MSGSIZE);
   write(p[1], msg2, MSGSIZE);
   write(p[1], msg3, MSGSIZE);

   /* 파이프로부터 읽는다. */
   for(j = 0; j < 3; j++){
      read (p[0], inbuf, MSGSIZE);
      printf ("%s\n", inbuf);
   }
   exit (0);
}

실제로는 두개의 프로세스가 서로 read write해서 사용함

 

char *msg1 = "hello, world #1";
char *msg2 = "hello, world #2";
char *msg3 = "hello, world #3";
main(){
   char inbuf[MSGSIZE]; /* MSGSIZE 16 */
   int p[2], j; 
   pid_t pid;
   pipe(p);

   switch (pid = fork()){
   case -1: 
   		perror ("fork call"); 
   		exit (2);
   case 0:  /* 자식일 경우 파이프에 쓴다. */
      write (p[1], msg1, MSGSIZE);
      write (p[1], msg2, MSGSIZE);
      write (p[1], msg3, MSGSIZE);
      break;
   default: /* 부모일 경우 파이프로부터 읽는다. */
      for (j = 0; j < 3; j++){
         read (p[0], inbuf, MSGSIZE);
         printf ("%s\n", inbuf);
      }
      wait (NULL);
  }
  exit (0);
}

 

child 도 read할 수 있음 근데 동시에 parent와 child가 read write하면 full duplex가 될텐데, 그럼 confusion이 일어남. 

자기가 write하고 자기가 read할 수 도 있음 혼란이 발생함
이런걸 방지하기 위해서 parent하고 child가 둘다 파일디스크립터가 0,1두개가 다있음,
그 중에서 parent는 read만 하니까 0을 쓰고 1은 안씀 그래서 1은 close시키자 이거임
child도 1만 쓰니까 0은 close하자는 거임 child는 write만, parent는 read만 하자는 거임
그럼 half duplex가 되는 거임

 

만약에 3을 4로 바꾸면 
parent가 deadlock이 발생할 수도 있음. 그 close 해주면 deadlock 발생할 수 있음 
그럼 3에서 4로 바꿔줄 때 데드락 발생할수있음 wait하고 있는데, 일어나지 않을 이벤트를 기다리는 거임

 

 

main(){
   char inbuf[MSGSIZE];
   int p[2], j;
   pid_t pid;

   pipe(p);

   switch (pid = fork()){
   case -1: perror ("fork call"); exit (2);
   case 0:
      close (p[0]);
      write (p[1], msg1, MSGSIZE);
      write (p[1], msg2, MSGSIZE);
      write (p[1], msg3, MSGSIZE);
      break,
   default:
      close (p[1]);
      for (j = o; j < 3; j++){
         read (p[0], inbuf, MSGSIZE);
         printf ("%s\n", inbuf);
      }
      wait (NULL);
 }
 exit (0);
}

 

child는 p[0]를 close 시켜야하는데 안close하고 유효함, parent도 p[1]이 아직 유효한 상태임
여기서 j에서 3을 4로 바꾸면 child는 write를 3번할 거 아님, 근데 parent는 read를 네 번함
그럼 네 번째 읽을 때는 empty라서 waiting 할거임 그래서 block 됨 새로운 데이터가 write될때까지 기다려야함
child는 break 해서 exit했음 child는 사실 죽었음.

 

write가 죽어있으면 return 0를 즉각함. 근데 parent를 close안해서 p[1]이 여전히 살아있음
그럼 p[1]으로 write할 수 있는거아님. 지금 write할수있는 child는 사라졌지만 parent가 p1을 갖고 있어서
wirte를 하면 데이터가 들어올 가능성이 있어서 계속 block 됨

 

자기가 write도 못하면서 자기가 write할 때까지 자기가 기다리는 꼴이 됨 절대로 일어나지 않을 상황을 무한정기다리는 데드락 상황이 발생함 parent가 p[1]을 close 안해줬기 때매임

 

p[1]이 어디에도없는데 p[0]를 read하려고하면 바로 return 0로해서 데드락 걸리지 않음
단방향으로만 해야하고 서로 상대방에서 쓰지 않는 건 close함

 

The size of pipe

시스템에서 파이프의 사이즈는 무한 사이즈가 아님 유한 사이즈임
POSIX 표준은 512바이트인데 기존보다 더 크게 잡아놓는다고 함

커널에서 메모리를 maintain함 limit.h보면 상수가 있는데 그 상수가 최대 사이즈임 pipe buf

 

다중 프로세스 환경에서 여러 개의 프로세스가 같은 파이프를 사용할때 

최대 사이즈 pipe buf 보다 같거나 작은 사이즈로 하면 interleaved가 안됨

interleave라는 거는 time share하며 번갈아가면 실행한다는 거임

 

100바이트 write 하면 수행시간이 좀오래걸리는데, 

time share로 작동하면 이거를 완성하지 않은 상태에서 옮겼다가 번갈아가면서 실행될 수 있음 그걸 interleave라고 함

 

그게 가능하다면 100바이트를 다 write안하고 이 중 일부만 write하고,

interleaved돼서 다른 애가 100바이트 중 일부를 들어가고 또 interrupt걸려서 다른거 또 들어감 이상한 일이 발생함

a가 100바이트를 다 넣고 b가 100바이트 다 넣어야하는데 이상하게 들어가게 된 거임

 

그런데 pipe buf 보다 더 크게 write할 때는 interleave가 된다는 거임 예외인 상황임

 

512가 최대인데 1000바이트를 write한다면, 1000중에 반만 들어가게 될거임 

그럼 read쪽에서 파이프가 빌 때까지 block 되어야 함 이때는 interleave가 가능하다 이거임

 

/*파이프 사이즈를 알아보는 프로그램*/
int count;
void alrm_action (int signo) {
 printf ("write blocked after %d characters\n", count);
 exit (0);
}
main(){
  int p[2];
  int pipe_size;
  char c = 'x';  static struct sigaction act;

  act.sa_handler = alrm_action;
  sigfillset (&(act.sa_mask));
  pipe(p);
  pipe_size = fpathconf (p[0], _PC_PIPE_BUF);
  printf ("Maximum size of write to pipe: %d bytes\n", pipe_size);
  sigaction (SIGALRM, &act, NULL);

  while (1) {
    alarm (20);    write(p[1], &c, 1);    alarm(0);
    if ((++count % 1024) == 0) printf ("%d characters in pipe\n", count);
  }

 

main 에서 파이프를 만들고 파이프를 만들기 전에 sigaction alarm 시그널을 setting 하는 거임
act 의 handler를 alarm actin인데 그거 실행하는 거임 global 로 잡았음 count를 출력해주는 거임

sa_mask 해서 이거 실행동안 다른 시그널 못 받게 함 최대 파이프 사이즈를 찾아가지고 미리 출력해보는 프로그램

 

한 바이트씩 write함. wriite 성공하면 alarm 을 끔 그리고 1024마다 나머지가 0이 됨
1024 단위로 count를 증가시키고 파이프가 쭉 있지 않고, 쭉 파이프가 쌓이다가 어느순간 꽉차면 write가 기다림
기다리는데 무한정 아니고 20초 뒤에 sig alarm 해가지고 실행하는 거임 그때 count가 얼마인지 출력하는 거임

 

Maximum size of write to pipe: 32768 bytes

1024 characters in pipe

2048 characters in pipe

3072 characters in pipe

4096 characters in pipe

5120 characters in pipe

.

.

.

31744 characters in pipe

32768 characters in pipe

write blocked after 32768 characters

20초 경과하고 sig alarm  이 날라와서 handler 수행 출력하는거

 

Blocking & Non-Blocking

 

파이프를 사용해서 write하고 read할 때 , block 되는 경우가 있었음. 

write가 살아있는 상황에서 read를 하는데 empty가 되면 read가 block 됨
write할 때는 파이프가 꽉찼을 때, read해서 공간이 생길 때까지 block 됨

 

block 되는 상황이 있음에도 불구하고, block 되지 않게 setting 할 수 있다는 거임

open 할 때 O_NONBLOCK flag를 설정하면 open 이 block 되는 상황에도 불구하고 block 안됨

곧 바로 return 한다는 뜻임. 성공하든 실패하든 블락안됨

 

이미 open 되어있는 파일디스크립터에 대해서는 read write가 블락 안되게 설정할 수 있는데

fcntl로 O_NONBLOCK flag 를 turn on 해주면 블락이 안되게 설정할 수 있음

 

Non-Blocking read and write

1. use fstat:stat.st_size -> #characters in the pipe

fstat(p[1], &buf); 
if( buf.st_size >= PIPE_BUF)  return error;

의미 없음

 

2. use fcntl -> O_NONBLOCK flag

if(fcntl(p[1], F_SETFL, O_NONBLOCK)==-1)	   
	perror(“fcntl”);

 open 하거나 파일디스크립터 형태를 만들어놔야하는데, fcntl로 파일디스크립터에서 nonblock flag를 설정

꽉차면 block 되는데 이때 block 안하고 return 한다는 거임 

 

fcntl(p[1], F_SETFL, O_NONBLOCK)

p[1] write할 때 block되는데 write하다가 꽉 차면 곧바로 return 하는데 기다리지 않고 return 하면 실패한거라서 -1
errno은 다시 하라는 걸로 EAGAIN이 세팅이 됨 

 

fcntl(p[0], F_SETFL, O_NONBLOCK)

read쪽 파일디스크립터에 블락되는 상황은 파이프가 empty 

read할 때 블락되는 상황이어도 안 되게 할라면 이럼됨
절대 블락이 안되는데 비어있기 때문에 곧바로 return 하면 -1이 return 되고 errno는 EAGAIN

 

#include <fcntl.h>
#include <errno.h>
#define MSGSIZE 6
 
int parent (int *);
int child (int *);

char *msg1 = "hello";
char *msg2 = "bye!!";

main(){
 int pfd[2];

 if(pipe (pfd) == -1)
  fatal ("pipe call");

 if(fcntl(pfd[0],F_SETFL,O_NONBLOCK)==-1)
  fatal ("fcntl call");

 switch(fork()){
 case -1: fatal("fork call");
 case 0: child(pfd);
 default: parent(pfd);
 }
}

int parent (int p[2]){
  int nread;  char buf[MSGSIZE]; close (p[1]);
  for(;;){
  	switch (nread=read(p[0],buf,MSGSIZE)){
    case -1:
      if (errno == EAGAIN){ 
        printf ("(pipe empty)\n");
        sleep (1);
        break;
       } 
       else fatal ("read call");
    case 0: 
      printf ("End of conversation\n");
      exit (0);
    default: printf ("MSG=%s\n", buf);
    }
  }
}

int child(int p[2]){
  int count;
  close (p[0]);
  for (count= 0; count < 3; count++){
    write (p[1], msg1, MSGSIZE);
    sleep(3);
  }
  write (p[1], msg2, MSGSIZE); exit (0);
}

 

 

MSG=hello

(pipe empty)

(pipe empty)

(pipe empty)

MSG=hello

(pipe empty)

(pipe empty)

(pipe empty)

MSG=hello

(pipe empty)

(pipe empty)

(pipe empty)

MSG=bye!!

End of conversation

 

The popen(3) & pclose(3)

#include <stdio.h>
FILE *popen (const char *cmdstring, const char *type);
// returns : file pointer if ok, NULL on error
int pclose(FILE *fp);
// returns : termination status of cmdstring, or -1 on error

파이프를 쓰기 쉽게 만들기 위해서 라이브러리로 파이프를 더 쉽게 만들어줄 수 있는 라이브러리

popen 으로 명령 스트링을 넣어주고, 실행해주는 이거를 자동으로 파이프를 만들어서 해줌 pclose로 close 함

 

popen은 parent가 해주는 건데, popen을 하면, 이 popen이 child 를 만들고, 그 pipe를 만들어서 통신하는
모든 dirty work 까지는 아니고, 좀 복잡한 따분한 거를 편리하게 다 해준다는 거임 

child를 실행해주고 child가 cmd를 실행하도록 하고 그 모든 걸 다 해주는게 popen임

pclose는 커맨드를 실행하는 child가 exit할때까지 기다리는 wait하는 역할을 함 이걸 한번에 하는 간단한 역할

 

 

 

두번째 argument가 read라고 하면 parent 가 read가 됨 w면 parent가 write가 됨

 

int main(){
    FILE *read_fp;
    char buffer[BUFSIZ + 1];
    int chars_read;
    memset(buffer, '\0', sizeof(buffer));
    read_fp = popen("uname -a", "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        if (chars_read > 0) {
            printf("Output was:-\n%s\n", buffer);
        }
        pclose(read_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

$./popen

Output was:-

Linux sloven 2.6.18-8.el5PAE #1 SMP Tue Jun 5 23:39:57 EDT 2007 i686 i686 i38
$ GNU/Linux


FIFO

디스크에 저장되는 파일임

파이프는 공통 조상이 파이프를 만들었을 때, 관련된 프로세스 사이에서만 쓸 수 있음 

f를 공유하는 프로세스랑 공유하지 않는 프로세스간의 unrelated 이런 관계를 unrelated라고 표현함

unrelated는 pipe를 못씀. 이런 한계가 있어서 unrelated 관계에도 파이프를 쓸수 있게 하기위해서 fifo가 나옴

 

파일이라서 owner가 있음. access permission 이 주어짐 파일이라서 open close 가능

정상적인 다른 파일처럼 delete도 가능.

 

파이프로 쓸라면 readonly나 write only 둘중 하나로 사용해야함
경우에 따라서 read write 로 하면 안된다고 했는데, 예외가 있음 read write로 open 해야하는 case가 있음
일반적으로는 readonly writeonly로 만 open 함 fifo 도 half duplex로 사용됨

 

또 다른 형태의 파이프라서 파이프가 작동하는 방식이랑 똑같음

파이프에다가 read할 때는 not empty인 경우 read함 immediately, return / empty인 경우 block until write

write는 pipe가 not full 이면 write immediately, return / full 인 경우 block until read and make room

 

write가 terminate인 상태에서 pipe가 read하려면 empty일때 return 0 

read가 terminate인 상태에서 write를 하려고 하면 kernel에서 sigpipe를 보내서 default action으로 terminate
catch 해서 ignore 또는 handler 실행 가능 lseek는 쓸 수 없음 
fifo가 디스크파일이라고 하더라도 일반 디스크파일이아니라서 lseek를 쓸수없음 쓰면 ESPIPE에러넘버가 세팅됨

 

The mkfifo(2) system call

#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
// returns : 0 if ok, -1 on error

 

file 형태의 파이프를 만드는 명령어 mkfifo (2) 시스템콜

 % /etc/mknod fifofile 이런식으로 p 옵션 넣어서 엔터하면 fifo 파일로 만들어짐

 

mkfifo ("/tmp/fifo", 0666);

.

fd = open("/tmp/fifo", O_WRONLY);

----------------------------------------------------------------------------

//if no process has the FIFO open for reading

if ((fd = open(“tmp/fifo”, O_WRONLY|O_NONBLOCK))==-1)

   perror(“open on fifo”);

----------------------------------------------------------------------------

if ((fd = open(“tmp/fifo”, O_RDONLY|O_NONBLOCK))==-1)

   perror(“open on fifo”);

 

상대편이 read로 open 한게 없는 경우에 write로 open 하면 block 된다고 했음

근데 non block 으로 flag를 setting하면 open이 block 할 상황에도 block 을 하지 않음

block 을 안하면 곧바로 return 한단 소리임

 

이 경우에는 read가 없는 상태에서 write만 open한다면 -1 return해서 실패함 read가 없는 경우

이 경우 실패하지 않고 성공하게 내버려두면 상황이 심각해짐

read가 없는 상태에서 write하면 pipe가 아니면 sigpipe가 날라옴 그러면 무조건 실패됨 error no는 ENXIO

 

read only면 block 하고 기다려야하는데 non block 으로 setting 하면 기다리지 않음 이 경우에는 실패로 리턴 x

성공해도 심각하지 않음 write가 없는 상태에서 read를 하면 pipe가 비어있고, 곧바로 0을 return

 

non block 으로 setting 한 경우, block 될 상황에서도 block 안되고 바로 return 
read only인데 write가 아직 open 안하면 에러 안생기고 0을 return 함
write only인데 read가 아직 open 안하면 커널에서 sigpipe가 날라오는 심각한 상황이 발생하니까 실패로 return 함

 

non block 지정안되면, 기다려야하는 상황에 block 함, write먼저 하면 read open 할때까지 기다린다는 말임
fifo가 read가 불려질때, 상대편에서 close 하면 pipe 하고 똑같은 read하는데 상대편에서 write가 close 됐다면
read는 곧바로 0을 return 함. read가 close 됐는데 write가 close 되면 sigpipe가 커널로부터 날라옴

 

/* sendmessage --  FIFO를 통해 메시지를 보낸다. */
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#define MSGSIZ		63
char *fifo = "fifo";

main(int argc, char **argv){
  int fd, j, nwrite;     char msgbuf[MSGSIZ+1];

  if(argc < 2){
    fprintf (stderr, "Usage: sendmessage msg ... \n"); exit(1);
  }

  if ((fd = open(fifo, O_WRONLY|O_NONBLOCK))<0) fatal("fifo open failed");

  for ( j = 1; j < argc; j++){ /* send messages */
    if(strlen(argv[j]) > MSGSIZ){
      fprintf (stderr, "message too long %s\n", argv[j]);
      continue;
    }
    strcpy (msgbuf, argv[j]);
    if((nwrite=write(fd,msgbuf,MSGSIZ+1))==-1) fatal("message write failed");
  }
  exit (0);
}

 

$ sendmessage msg1 msg2 msg3

msg가 하나라도 있으면 non block 으로 open 함 write only, 반대편에서 open 하지 않으면 fatal err msg 하고 exit
return 해버림 

성공해서 넘어오면 상대편에서 read only로 open 한게 있다는 말임. 

상대 프로세스가 없으면 non block 이라서 -1 return
msgbuf에 copy 해서 fifo에 write 함 +1이라는거 msg 끝에 null character 있다는 거임 

실패한다는 거는 상대편에서 못받아주는 에러가 생겼다는 거임 성공하면 다음 메시지를 출력하도록 돌아감

 

/* rcvmessage -- fifo를 통해 메시지를 받는다. */
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#define MSGSIZ		63
char *fifo = "fifo";

main (int argc, char **argv){
  int fd;
  char msgbuf[MSGSIZ+1];

  if (mkfifo(fifo, 0666) == -1){
    if (errno != EEXIST) fatal ("receiver: mkfifo");
  }

  if ((fd = open(fifo, O_RDWR)) < 0) fatal ("fifo open failed");

  for(;;){
    if (read(fd, msgbuf, MSGSIZ+1) <0) fatal ("message read failed");

 /*
  * 메시지를 프린트한다 ; 실제로는 보다 흥미 있는 일이 수행된다.
  */
    printf ("message received:%s\n", msgbuf);
  }
}

 

revmsg에서 fifo 를 만듦 에러 -1이 났다면 이미 존재한걸 또 만들어서임

이미 있으면 open 하고 없으면 만들어서 open 함 만들어놓고 그 fifo 에 대해서 rdwr로 open 함

rdonly로 하면 문제가 생겨서 그럼

 

read only로만 했다고 했을 때, 상대에 write가 없는 상태라면 send message가 아직 없다면 
for loop으로 read를 하는데, 아직 send msg 없으니까 write한게 없는 상태에서 read하면 곧바로 return 0 함

 

이게 반복 되면 cpu 100%이렇게 나옴. 

그러면 쓸데없이 cpu 계속 사용함 이걸 방지하기 위해서 

read only 대신에 rdwr함 rdwr 하면 sendmsg가 없다고 하더라도 write only가 들어 있기 때문에

sendmsg가 아직 없고 fifo 가 empty여도 write가 존재함. 그때 read하면 block 됨. 

 

rdwr했다고 하더라도 write는 하지 않고 read만 함 그래서 read하고 block 돼서 기다리고 있다가 sendmsg 실행
read가 open 이 되어있어서, write에 성공함. 

 

rev는 서버역할을 함 항상 살아있고 fifo 로부터 받을 준비가 되어있음 send는 일종의 client 처럼 역할을 함

공통 조상이 pipe를 실행하지 않아도 됨 related가 아니어도 pipe를 사용해서 msg 주고받을 수 있음