C언어 FIFO 파이프 만들기 함수 mkfifo()

2020. 3. 16. 08:01 컴퓨터/프로그래밍

C함수

pipe()에서 생성한 파이프를 이용하는 것은 부모와 자식 프로세스에서만 사용됩니다. 그러나 FIFO를 이용하면 서로 다른 프로세스에서 사용할 수 있으며, FIFO를 생성하는 파일 이름을 알고 있다면 누구나 사용할 수 있습니다.

mkfifo()함수로 FIFO 파일을 생성하면 이후로는 이 파일을 가지고 파이프로 이용할 수 있습니다. 또한 FIFO 파일이 이미 존재하면 에러가 발생하므로 새로 생성할 필요가 있다면 삭제하시고 실행합니다.

  • 헤더: sys/types.h, sys/stat.h
  • 형태: int mkfifo(const char *pathname, mode_t mode)
  • 인수: char *pathname 파이프로 사용할 파일 이름
    mode_t mode FIFO 파일에 대한 접근 권한
  • 반환: 0 == 성공, -1 == 실패(error에 에러 번호 설정)

 

예제

FIFO를 이용한 파이프는 pipe()에서 생성한 파이프와는 달리 서로 다른 애플리케이션에서 실행된 프로세스가 사용할 수 있습니다. 예제에서도 이와 같은 내용이 실제로 되는지 알기 위해 두 개의 소스를 준비했습니다.

하나는 FIFO를 만들고 상대로부터 데이터를 수신하는 main_receiver.c 이고, 다른 하나는 실행할 때 마다 파이프를 통해 자료를 전송하는 main_sender.c 입니다. main_reciever.c 는 receiver 로, main_sender.c 는 sender로 컴파일한 후 실행할 것입니다.

//////////////////////////////// main_receiver.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define  FIFO_FILE   "/tmp/fifo"
#define  BUFF_SIZE   1024

int main( void)
{
   int   counter = 0;
   int   fd;
   char  buff[BUFF_SIZE];

   if ( -1 == mkfifo( FIFO_FILE, 0666)){
      perror( "mkfifo() 실행에러");
      exit( 1);
   }
   if ( -1 == ( fd = open( FIFO_FILE, O_RDWR))){  //  <---- (A)
      perror( "open() 실행에러");
      exit( 1);
   }
   while( 1 ){
      memset( buff, 0, BUFF_SIZE);
      read( fd, buff, BUFF_SIZE);
      printf( "%d: %s\n", counter++, buff);
   }
   close( fd);
} 

//////////////////////////////// main_sender.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#define  FIFO_FILE   "/tmp/fifo"

int main( void)
{
   int   fd;
   char *str   = "badayak.com";

   if ( -1 == ( fd = open( FIFO_FILE, O_WRONLY))){
      perror( "open() 실행에러");
      exit( 1);
   }

   write( fd, str, strlen( str));
   close( fd);
} 

실행 결과

receiver sender
]$ gcc main_receiver.c -o receive.out
]$ ./receiver.out
0: badayak.com
1: badayak.com
2: badayak.com
3: badayak.com
4: badayak.com
]$ gcc main_sender.c -o sender.out
]$ ./sender.out
]$ ./sender.out
]$ ./sender.out
]$ ./sender.out
]$ ./sender.out
]$ ./sender.out

그런데 한가지 이해가 안되는 것이 있습니다. main_receiver.c 는 모두 FIFO 파이프에서 자료를 읽어 들이기만 하는 프로그램임에도 (A) 가 가르키는 open()의 mode를 보시면 O_RDONLY 가 아니라 O_RDWR 입니다. 소스 코드를 O_RDONLY 로 바꾸고 실행하면 sender에서 한 번만 자료를 전송했는데도, receiver 가 계속 카운트를 올리면서 계속 카운터를 출력할 것입니다.

너무 빠르게 스크롤되기 때문에 하는 수 없이 따로 터미널을 연결하여 receiver를 kill 해야 합니다.

이렇게 되는 이유는 자료를 정송하는 main_sender.c 를 아래와 같이 close() 함수 전에 무한 루프를 돌려 보면 이유를 알 수 있습니다.

      exit( 1);
   }
   if ( -1 == ( fd = open( FIFO_FILE, O_RDONLY))){ //  RDONLY로 변경
      perror( "open() 실행에러");
      exit( 1);
   }
   while( 1 ){
      memset( buff, 0, BUFF_SIZE);
      read( fd, buff, BUFF_SIZE);
      printf( "%d: %s\n", counter++, buff);
      sleep( 1) ;                                 //  1초 대기 루틴 삽입
   }
   close( fd);

//////////////////////////////// main_sender.c

   if ( -1 == ( fd = open( FIFO_FILE, O_WRONLY))){
      perror( "open() 실행에러");
      exit( 1);
   }
   write( fd, str, strlen( str));
   
   while( 1 )                                    //  대기 루틴 삽입
       ;
   
   close( fd);
} 
receiver sender
]$ gcc main_receiver.c -o receive.out
]$ ./receiver.out
0: badayak.com
1:           <---- sender.out에서 Ctrl-C 키 누르면 
2:                  1초마다 문자열 출력
3:
4:
5:
6:
^C
]$
]$ gcc main_sender.c -o sender.out
^C                   <---- Ctrl-C 키 누름
]$

Ctrl-C로 sender 를 종료하면 이제 FIFO는 오로지 receiver 만이 사용하게 되고, receiver는 FIFO를 읽기 전용으로 open()했기 때문에 이 파이프에는 누구도 쓰기를 할 수 있는 프로세스가 없게 됩니다.

이렇게 되면, 즉 누구도 파이프에 자료를 쓸 수 없는 상태가 되면 read() 함수는 바로 return 값을 0으로 바로 복귀됩니다. 이전에는 자료가 올 때까지 대기했지만 이제는 쓰기할 프로세스가, 자료를 전송할 프로세스가 없는 만큼 대기할 필요가 없기 때문이죠.

이런 이유로 파이프에서 자료를 읽기만 하는 프로세스도 O_RDWR 옵션을 주어 나라도 쓰기를 할 수 있다라고 등록해서 read() 함수에서 바로 복귀되는 것을 막습니다. 파이프의 특성 중 하나가 자기가 보낸 것이라도 남이 받기 전에 내가 먼저 읽기를 하면 받을 수 있기 때문입니다. ^^

이 댓글을 비밀 댓글로