UNIX Special Files - 시스템프로그래밍

UNIX Special Files - 시스템프로그래밍

Tag
Computer Science Engineering
System Programming

Pipe

  • 프로세스간에 통신을 하기 위해선 OS의 도움을 받아야 한다.
    • InterProcess Communication (IPC) 메커니즘 중 하나를 요청해야 하는 것
  • 가장 간단한 버젼의 UNIX interprocess communication mechanism이 바로 pipe 이다.
    • file로 표현이 되기 때문에, read, write 함수를 그대로 사용하여 I/O를 수행할 수 있다.
  • 데이터가 흘러가는 관이라고 생각하자.
    • 파이프에 데이터를 넣을 때 write / 파이프로부터 데이터를 받을 때 read 함수를 호출한다.
    • 당연히 같은 컴퓨터 시스템에 돌아가고 있는 프로세스 사이에 정보를 공유하기 위해 사용하는 메커니즘 이다.
 

Pipe

#include <unistd.h> int pipe(int fd[2]);
  • pipe객체를 생성 해주고, 만들어진 special file을 open 해준다.
    • 파라미터값에 file descriptor가 리턴된다. ⇒ 배열 안에
  • fd[0]: 읽기 용
  • fd[1]: 쓰기 용
  • 데이터가 들어오고 나갈때는 FIFO (first-in-first-out) 형태로 이루어진다.
  • 성공되었을 경우 0 / 실패했을 경우 -1 리턴
 

Characteristics of pipe I

  • 이름이 없다.
    • directory entry에 나타나지 않는다.
    • 이름이 없기 때문에 꼭 file descriptor 정보를 알고 있어야 access가 가능하다.
  • temporary한 객체이다.
    • pipe를 사용하는 프로세스가 모두 종료되면, 해당 pipe 객체도 소멸된다. ( 현재 설명하는 이름없는 pipe의 경우만 )
  • pipe를 사용할 수 있는 프로세스
    • pipe를 생성한 프로세스
    • fork를 통해 file descriptor를 상속받은 자식 / 자손 프로세스
  • 주의해야할 점
    • fd[0]를 write / fd[1]를 read 용도로 사용하면 오동작이 일어날 것이다.
    • 잘못사용했을 때 어떤 일이 일어나는지를 명시하고 있지 않다.
  • read를 하는 경우
    • read함수가 즉각적으로 return하는 경우pipe가 비어있지 않을 때이다
      • 이미 누군가가 pipe에 데이터를 썼을 경우
    • pipe가 비어있고, 다른 프로세스가 pipe에 write operation용으로 open해놓은 상황이라면
      • 어떤 내용이 파이프에 쓰여질 때 까지 read함수는 block이 된다.
    • pipe가 비어있고, 아무 프로세스도 pipe를 write operation용으로 open하지 않은 상황이라면
      • read함수는 바로 0을 return 한다. ( EOF condition )
  • pipe에 대하여 I/O를 수행하는 것은 blocking I/O를 수행하는 것이다.
 

Example

하나의 프로세스가 파이프 객체에 쓰고 읽는 예시
int main(void) { int pipefd[2]; int i; char s[1000]; char *s2; if (pipe(pipefd) < 0) { perror("pipe"); exit(1); } s2 = "Rex Morgan MD"; write(pipefd[1], s2, strlen(s2)); i = read(pipefd[0], s, 1000); s[i] = '\0'; printf("Read %d bytes from the pipe:%s'\n", i, s); }
  • pipe함수를 호출하면 pipe 객체가 생성되고, 두 개의 file descriptor가 생성된다.
    • pipefd[1]을 통해서 쓰고, pipefd[0]을 통해서 읽을 수 있다.
  • process가 pipe에 end-of-file character (ctrl+d)를 입력하게 되면 파일의 끝임을 알려줄 수 있다.
    • notion image
 

Two steps to make PIPE

notion image
notion image
 

Example

  • 부모프로세스가 자식프로세스에게 “hello”라는 메시지를 전달하고 싶음
  • child buffer는 “empty”라는 값이 쓰여진 상태이다.
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 10 int main(void) { char bufin[BUFSIZE] = "empty"; char bufout[] = "hello"; int bytesin; pid_t childpid; int fd[2]; if (pipe(fd) == -1) { perror("Failed to create the pipe"); return 1; } bytesin = strlen(bufin); childpid = fork(); if (childpid == -1) { perror("Failed to fork"); return 1; } if (childpid) /* parent code */ write(fd[1], bufout, strlen(bufout)); else /* child code */ bytesin = read(fd[0], bufin, BUFSIZE); fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n", (long)getpid(), bytesin, bufin, bufout); return 0; }
notion image
 

Question

  • 자식 프로세스는 모든 string을 항상 읽어오는가?
    • 부모의 bufin은 항상 “empty”
    • 자식의 bufin은 pipe로 받은 값인 “hello”로 덮여져야 한다.
    • read함수가 다 읽어온다는 보장은 없기 때문에 항상 모든 string을 읽는다는 보장은 없다.
    • child의 bufin이 helty와 같이, 세 바이트 “hel”만 읽어들일 수도 있다.
  • 부모프로세스가 write하기 이전에 자식프로세스가 먼저 pipe를 read 호출할 수도 있는데, 이때 못읽는 것이 아니라 부모프로세스가 쓰기 용으로 open 해두었기 때문에 block 된다.
 

FIFO (named pipe)

  • pipe는 temporary한 특징이 있다.
    • 모든 프로세스들이 종료되면 사용되었던 pipe 또한 OS에 의해 소멸된다.
    • 이름이 없다.
  • FIFO는 이름이 있는 pipe이다.
  • 일반 파일을 사용했던 것 처럼, FIFO객체를 만들 때에도 permission 정보를 주어야한다.
    • ls 로 확인 가능
  • 다른 프로세스도 이름을 알면, 그리고 접근 권한이 있으면 해당 FIFO를 사용할 수 있다.
 

FIFO

#include <sys/stat.h> int mkfifo(const char *path, mode_t mode);
  • path: 파일의 경로명 ( 이름 )
  • mode: access permission
 
  • FIFO를 만드는 방법
    • shell에서 mkfifo command
    • program내부에서 mkfifo 함수 호출
  • mkfifo 함수를 호출하면 새로운 FIFO 객체가 생성이 된다.
    • open이 되지 않는다! ⇒ 얘는 이름이 있잖아
  • FIFO를 삭제하는 방법
    • shell에서 rm command
    • program내부에서 unlink 함수 호출
    •  

Example

  • 현재 작업 디렉터리에 이름은 myfifo인 FIFO 객체를 만들고 싶다.
  • 모두가 읽을 수 있고, owner만 쓸 수 있도록.
 
#define FIFO_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) if (mkfifo(“myfifo”, FIFO_PERMS) == -1) perror(“Failed to create myfifo”);
 

Example

  • myfifo라는 FIFO객체를 삭제하고 싶다.
if (unlink(“myfifo”) == -1) perror(“Failed to remove myfifo”);
 

Example

  • 문제
      1. command line으로부터 명시된 path를 받아서 named pipe를 생성
      1. 자식프로세스 생성
      1. 자식프로세스가 named pipe에 write
      1. 부모프로세스가 자식프로세스가 적은 내용을 read

parentchildfifo.c

#define BUFSIZE 256 #define FIFO_PERM (S_IRUSR | S_IWUSR) // 자식프로세스가 부모에게 FIFO를 통해서 메시지 전달 int dofifochild(const char *fifoname, const char *idstring); // 부모프로세스가 FIFO를 통해서 메시지 읽음 int dofifoparent(const char *fifoname); int main (int argc, char *argv[]) { pid_t childpid; if (argc != 2) { /* command line has pipe name */ fprintf(stderr, "Usage: %s pipename\n", argv[0]); return 1; } if (mkfifo(argv[1], FIFO_PERM) == -1) { /* create a named pipe */ // 이미 존재하는건 그냥 무시해버리기 if (errno != EEXIST) { fprintf(stderr, "[%ld]:failed to create named pipe %s: %s\n", (long)getpid(), argv[1], strerror(errno)); return 1; } } if ((childpid = fork()) == -1){ perror("Failed to fork"); return 1; } if (childpid == 0) /* The child writes */ return dofifochild(argv[1], "this was written by the child"); else return dofifoparent(argv[1]); }
 

dofifochild.c

#define BUFSIZE 256 int dofifochild(const char *fifoname, const char *idstring) { char buf[BUFSIZE]; int fd; int rval; ssize_t strsize; fprintf(stderr, "[%ld]:(child) about to open FIFO %s...\n", (long)getpid(), fifoname); // open 해라! while (((fd = open(fifoname, O_WRONLY)) == -1) && (errno == EINTR)); if (fd == -1) { fprintf(stderr, "[%ld]:failed to open named pipe %s for write: %s\n", (long)getpid(), fifoname, strerror(errno)); return 1; } // ex) [1234]: this was written by the child rval = snprintf(buf, BUFSIZE, "[%ld]:%s\n", (long)getpid(), idstring); if (rval < 0) { fprintf(stderr, "[%ld]:failed to make the string:\n", (long)getpid()); return 1; } // null character를 포함한 길이 strsize = strlen(buf) + 1; fprintf(stderr, "[%ld]:about to write...\n", (long)getpid()); rval = r_write(fd, buf, strsize); if (rval != strsize) { fprintf(stderr, "[%ld]:failed to write to pipe: %s\n", (long)getpid(), strerror(errno)); return 1; } fprintf(stderr, "[%ld]:finishing...\n", (long)getpid()); return 0; }
snprintf: 버퍼에 쓰는 함수 / 버퍼의 끝에 자동으로 null character를 넣어준다.
  • 2번째 인자 이상으로는 쓰지 않는다. ( 정해진 개수만큼 쓰겠다 )
 

dofifoparent.c

#define BUFSIZE 256 #define FIFO_MODES O_RDONLY int dofifoparent(const char *fifoname) { char buf[BUFSIZE]; int fd; int rval; fprintf(stderr, "[%ld]:(parent) about to open FIFO %s...\n", (long)getpid(), fifoname); while (((fd = open(fifoname, FIFO_MODES)) == -1) && (errno == EINTR)) ; if (fd == -1) { fprintf(stderr, "[%ld]:failed to open named pipe %s for read: %s\n", (long)getpid(), fifoname, strerror(errno)); return 1; } fprintf(stderr, "[%ld]:about to read...\n", (long)getpid()); rval = r_read(fd, buf, BUFSIZE); if (rval == -1) { fprintf(stderr, "[%ld]:failed to read from pipe: %s\n", (long)getpid(), strerror(errno)); return 1; } fprintf(stderr, "[%ld]:read %.*s\n", (long)getpid(), rval, buf); return 0; }
 

Question

  • 프로세스들이 모두 종료되게 되면, named pipe는 어떻게 되는가?
    • 프로세스가 모두 종료를 하더라도, named pipe는 남아있게 된다.